Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

Commit

Permalink
GH-26: Add SMS API (#91)
Browse files Browse the repository at this point in the history
* Adding SMS API

* Added tests

* Added docs

* Made some changes to the way SMS works

* Fix the tests
  • Loading branch information
mattleibow authored and jamesmontemagno committed Mar 21, 2018
1 parent 608c70d commit 13bb42c
Show file tree
Hide file tree
Showing 23 changed files with 558 additions and 8 deletions.
14 changes: 14 additions & 0 deletions Caboodle.Tests/Sms_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.Caboodle.Tests
{
public class Sms_Tests
{
[Fact]
public Task Sms_Fail_On_NetStandard()
{
return Assert.ThrowsAsync<NotImplementedInReferenceAssemblyException>(() => Sms.ComposeAsync());
}
}
}
7 changes: 7 additions & 0 deletions Caboodle/Platform/Platform.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ internal static bool HasPermissionInManifest(string permission)
return requestedPermissions?.Any(r => r.Equals(permission, StringComparison.InvariantCultureIgnoreCase)) ?? false;
}

internal static bool IsIntentSupported(Intent intent)
{
var manager = CurrentContext.PackageManager;
var activities = manager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly);
return activities.Any();
}

public static void BeginInvokeOnMainThread(Action action)
{
if (handler?.Looper != Looper.MainLooper)
Expand Down
33 changes: 33 additions & 0 deletions Caboodle/Platform/Platform.ios.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Linq;
using Foundation;
using UIKit;

namespace Microsoft.Caboodle
{
Expand All @@ -15,5 +17,36 @@ public static void BeginInvokeOnMainThread(Action action)

NSRunLoop.Main.BeginInvokeOnMainThread(action.Invoke);
}

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;
}
}
}
17 changes: 17 additions & 0 deletions Caboodle/Shared/Exceptions.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,21 @@ public PermissionException(string permission)
{
}
}

public class FeatureNotSupportedException : NotSupportedException
{
public FeatureNotSupportedException()
{
}

public FeatureNotSupportedException(string message)
: base(message)
{
}

public FeatureNotSupportedException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
66 changes: 66 additions & 0 deletions Caboodle/Sms/Sms.android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Threading.Tasks;
using Android.Content;
using Android.OS;
using Android.Provider;

using AndroidUri = Android.Net.Uri;

namespace Microsoft.Caboodle
{
public static partial class Sms
{
static bool IsComposeSupported
=> Platform.IsIntentSupported(CreateIntent("0000000000"));

public static Task ComposeAsync(SmsMessage message)
{
if (!IsComposeSupported)
throw new FeatureNotSupportedException();

var intent = CreateIntent(message);
Platform.CurrentContext.StartActivity(intent);

return Task.FromResult(true);
}

static Intent CreateIntent(SmsMessage message)
=> CreateIntent(message?.Recipient, message?.Body);

static Intent CreateIntent(string recipient, string body = null)
{
Intent intent;
if (!string.IsNullOrWhiteSpace(recipient))
{
var uri = AndroidUri.Parse("smsto:" + recipient);
intent = new Intent(Intent.ActionSendto, uri);

if (!string.IsNullOrWhiteSpace(body))
intent.PutExtra("sms_body", body);
}
else
{
var pm = Platform.CurrentContext.PackageManager;
var packageName = Telephony.Sms.GetDefaultSmsPackage(Platform.CurrentContext);
intent = pm.GetLaunchIntentForPackage(packageName);
}

return intent;
}

public static string GetDefaultSmsPackage(Context context)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
{
return Telephony.Sms.GetDefaultSmsPackage(context);
}
else
{
var defApp = Settings.Secure.GetString(context.ContentResolver, "sms_default_application");
var pm = context.ApplicationContext.PackageManager;
var intent = pm.GetLaunchIntentForPackage(defApp);
var mInfo = pm.ResolveActivity(intent, 0);
return mInfo.ActivityInfo.PackageName;
}
}
}
}
38 changes: 38 additions & 0 deletions Caboodle/Sms/Sms.ios.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Threading.Tasks;
using MessageUI;

namespace Microsoft.Caboodle
{
public static partial class Sms
{
static bool IsComposeSupported
=> MFMessageComposeViewController.CanSendText;

public static Task ComposeAsync(SmsMessage message)
{
if (!IsComposeSupported)
throw new FeatureNotSupportedException();

// do this first so we can throw as early as possible
var controller = Platform.GetCurrentViewController();

// create the controller
var messageController = new MFMessageComposeViewController();
if (!string.IsNullOrWhiteSpace(message?.Body))
messageController.Body = message.Body;
if (!string.IsNullOrWhiteSpace(message?.Recipient))
messageController.Recipients = new[] { message.Recipient };

// show the controller
var tcs = new TaskCompletionSource<bool>();
messageController.Finished += (sender, e) =>
{
messageController.DismissViewController(true, null);
tcs.SetResult(e.Result == MessageComposeResult.Sent);
};
controller.PresentViewController(messageController, true, null);

return tcs.Task;
}
}
}
10 changes: 10 additions & 0 deletions Caboodle/Sms/Sms.netstandard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;

namespace Microsoft.Caboodle
{
public static partial class Sms
{
public static Task ComposeAsync(SmsMessage message)
=> throw new NotImplementedInReferenceAssemblyException();
}
}
27 changes: 27 additions & 0 deletions Caboodle/Sms/Sms.shared.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Threading.Tasks;

namespace Microsoft.Caboodle
{
public static partial class Sms
{
public static Task ComposeAsync()
=> ComposeAsync(null);
}

public class SmsMessage
{
public SmsMessage()
{
}

public SmsMessage(string body, string recipient)
{
Body = body;
Recipient = recipient;
}

public string Body { get; set; }

public string Recipient { get; set; }
}
}
27 changes: 27 additions & 0 deletions Caboodle/Sms/Sms.uwp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Chat;
using Windows.Foundation.Metadata;

namespace Microsoft.Caboodle
{
public static partial class Sms
{
static bool IsComposeSupported
=> ApiInformation.IsTypePresent("Windows.ApplicationModel.Chat.ChatMessageManager");

public static Task ComposeAsync(SmsMessage message)
{
if (!IsComposeSupported)
throw new FeatureNotSupportedException();

var chat = new ChatMessage();
if (!string.IsNullOrWhiteSpace(message?.Body))
chat.Body = message.Body;
if (!string.IsNullOrWhiteSpace(message?.Recipient))
chat.Recipients.Add(message.Recipient);

return ChatMessageManager.ShowComposeSmsMessageAsync(chat).AsTask();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Geocoding_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Preferences_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FileSystem_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Sms_Tests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Traits.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils.cs" />
</ItemGroup>
Expand Down
34 changes: 34 additions & 0 deletions DeviceTests/Caboodle.DeviceTests.Shared/Sms_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Microsoft.Caboodle;
using Xunit;

namespace Caboodle.DeviceTests
{
public class Sms_Tests
{
[Fact]
public Task Sms_ComposeAsync_Does_Not_Throw()
{
return Utils.OnMainThread(() =>
{
if (Utils.IsiOSSimulator)
return Assert.ThrowsAsync<FeatureNotSupportedException>(() => Sms.ComposeAsync());
else
return Sms.ComposeAsync();
});
}

[Fact]
public Task Sms_ComposeAsync_Does_Not_Throw_When_Empty()
{
var message = new SmsMessage();
return Utils.OnMainThread(() =>
{
if (Utils.IsiOSSimulator)
return Assert.ThrowsAsync<FeatureNotSupportedException>(() => Sms.ComposeAsync(message));
else
return Sms.ComposeAsync(message);
});
}
}
}
4 changes: 4 additions & 0 deletions DeviceTests/Caboodle.DeviceTests.Shared/Utils.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using System;
using System.Threading.Tasks;
using Microsoft.Caboodle;

namespace Caboodle.DeviceTests
{
public class Utils
{
public static bool IsiOSSimulator
=> DeviceInfo.DeviceType == DeviceType.Virtual && DeviceInfo.Platform == DeviceInfo.Platforms.iOS;

#if WINDOWS_UWP
public static async Task OnMainThread(Windows.UI.Core.DispatchedHandler action)
{
Expand Down
7 changes: 4 additions & 3 deletions Samples/Caboodle.Samples/App.xaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:Caboodle.Samples.Converters"
x:Class="Caboodle.Samples.App">
<Application.Resources>

<!-- Application resource dictionary -->

<ResourceDictionary>
<converters:NegativeConverter x:Key="NegativeConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
25 changes: 25 additions & 0 deletions Samples/Caboodle.Samples/Converters/NegativeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Globalization;
using Xamarin.Forms;

namespace Caboodle.Samples.Converters
{
public class NegativeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool v)
return !v;
else
return false;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool v)
return !v;
else
return true;
}
}
}

0 comments on commit 13bb42c

Please sign in to comment.