mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 02:18:27 +03:00
auth activity for locked vaults when autofilling
This commit is contained in:
parent
0a6767209d
commit
322b251def
7 changed files with 1256 additions and 405 deletions
|
@ -201,6 +201,12 @@
|
||||||
<HintPath>..\..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Animated.Vector.Drawable.dll</HintPath>
|
<HintPath>..\..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Animated.Vector.Drawable.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Xamarin.Android.Support.Constraint.Layout, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Xamarin.Android.Support.Constraint.Layout.1.0.2.2\lib\MonoAndroid70\Xamarin.Android.Support.Constraint.Layout.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Xamarin.Android.Support.Constraint.Layout.Solver, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\..\packages\Xamarin.Android.Support.Constraint.Layout.Solver.1.0.2.2\lib\MonoAndroid70\Xamarin.Android.Support.Constraint.Layout.Solver.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Xamarin.Android.Support.Design, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="Xamarin.Android.Support.Design, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\..\packages\Xamarin.Android.Support.Design.23.3.0\lib\MonoAndroid43\Xamarin.Android.Support.Design.dll</HintPath>
|
<HintPath>..\..\packages\Xamarin.Android.Support.Design.23.3.0\lib\MonoAndroid43\Xamarin.Android.Support.Design.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
|
@ -287,6 +293,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="AutofillActivity.cs" />
|
<Compile Include="AutofillActivity.cs" />
|
||||||
<Compile Include="AutofillCredentials.cs" />
|
<Compile Include="AutofillCredentials.cs" />
|
||||||
|
<Compile Include="Autofill\AuthActivity.cs" />
|
||||||
<Compile Include="Autofill\Field.cs" />
|
<Compile Include="Autofill\Field.cs" />
|
||||||
<Compile Include="Autofill\FieldCollection.cs" />
|
<Compile Include="Autofill\FieldCollection.cs" />
|
||||||
<Compile Include="Autofill\AutofillService.cs" />
|
<Compile Include="Autofill\AutofillService.cs" />
|
||||||
|
@ -353,6 +360,9 @@
|
||||||
<AndroidResource Include="Resources\layout\autofill_listitem.axml">
|
<AndroidResource Include="Resources\layout\autofill_listitem.axml">
|
||||||
<SubType>AndroidResource</SubType>
|
<SubType>AndroidResource</SubType>
|
||||||
</AndroidResource>
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\layout\autofill_authactivity.axml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</AndroidResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Properties\AndroidManifest.xml" />
|
<None Include="Properties\AndroidManifest.xml" />
|
||||||
|
|
114
src/Android/Autofill/AuthActivity.cs
Normal file
114
src/Android/Autofill/AuthActivity.cs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Views;
|
||||||
|
using Android.Widget;
|
||||||
|
using Android.Support.V7.App;
|
||||||
|
using Android.Views.Autofill;
|
||||||
|
using Android.App.Assist;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
|
||||||
|
namespace Bit.Android.Autofill
|
||||||
|
{
|
||||||
|
[Activity(Label = "AuthActivity")]
|
||||||
|
public class AuthActivity : AppCompatActivity
|
||||||
|
{
|
||||||
|
private EditText _masterPassword;
|
||||||
|
private Intent _replyIntent = null;
|
||||||
|
|
||||||
|
protected override void OnCreate(Bundle state)
|
||||||
|
{
|
||||||
|
base.OnCreate(state);
|
||||||
|
SetContentView(Resource.Layout.autofill_authactivity);
|
||||||
|
var masterLoginLabel = FindViewById(Resource.Id.master_login_header) as TextView;
|
||||||
|
masterLoginLabel.Text = AppResources.VerifyMasterPassword;
|
||||||
|
|
||||||
|
|
||||||
|
var masterPasswordLabel = FindViewById(Resource.Id.password_label) as TextView;
|
||||||
|
masterPasswordLabel.Text = AppResources.MasterPassword;
|
||||||
|
|
||||||
|
_masterPassword = FindViewById(Resource.Id.master_password) as EditText;
|
||||||
|
|
||||||
|
var loginButton = FindViewById(Resource.Id.login) as TextView;
|
||||||
|
loginButton.Text = AppResources.LogIn;
|
||||||
|
loginButton.Click += (sender, e) => {
|
||||||
|
Login();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var cancelButton = FindViewById(Resource.Id.cancel) as TextView;
|
||||||
|
cancelButton.Text = AppResources.Cancel;
|
||||||
|
cancelButton.Click += (sender, e) => {
|
||||||
|
_replyIntent = null;
|
||||||
|
Finish();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Finish()
|
||||||
|
{
|
||||||
|
if(_replyIntent != null)
|
||||||
|
{
|
||||||
|
SetResult(Result.Ok, _replyIntent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetResult(Result.Canceled);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Login()
|
||||||
|
{
|
||||||
|
var password = _masterPassword.Text;
|
||||||
|
if(true) // Check password
|
||||||
|
{
|
||||||
|
Success();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Toast.MakeText(this, "Password incorrect", ToastLength.Short).Show();
|
||||||
|
_replyIntent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Success()
|
||||||
|
{
|
||||||
|
var structure = Intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure) as AssistStructure;
|
||||||
|
if(structure == null)
|
||||||
|
{
|
||||||
|
_replyIntent = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parser = new Parser(structure);
|
||||||
|
parser.ParseForFill();
|
||||||
|
if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri))
|
||||||
|
{
|
||||||
|
_replyIntent = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = await AutofillHelpers.GetFillItemsAsync(Resolver.Resolve<ICipherService>(), parser.Uri);
|
||||||
|
if(!items.Any())
|
||||||
|
{
|
||||||
|
_replyIntent = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = AutofillHelpers.BuildFillResponse(this, parser.FieldCollection, items);
|
||||||
|
_replyIntent = new Intent();
|
||||||
|
_replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,56 +5,70 @@ using Android.Service.Autofill;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Android.App;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Android.Autofill
|
namespace Bit.Android.Autofill
|
||||||
{
|
{
|
||||||
public static class AutofillHelpers
|
public static class AutofillHelpers
|
||||||
{
|
{
|
||||||
public static FillResponse BuildFillResponse(Context context, bool auth, FieldCollection fields,
|
public static async Task<List<IFilledItem>> GetFillItemsAsync(ICipherService service, string uri)
|
||||||
IDictionary<string, IFilledItem> items)
|
{
|
||||||
|
var items = new List<IFilledItem>();
|
||||||
|
var ciphers = await service.GetAllAsync(uri);
|
||||||
|
if(ciphers.Item1.Any() || ciphers.Item2.Any())
|
||||||
|
{
|
||||||
|
var allCiphers = ciphers.Item1.ToList();
|
||||||
|
allCiphers.AddRange(ciphers.Item2.ToList());
|
||||||
|
foreach(var cipher in allCiphers)
|
||||||
|
{
|
||||||
|
items.Add(new CipherFilledItem(cipher));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FillResponse BuildFillResponse(Context context, FieldCollection fields, List<IFilledItem> items)
|
||||||
{
|
{
|
||||||
var responseBuilder = new FillResponse.Builder();
|
var responseBuilder = new FillResponse.Builder();
|
||||||
if(items != null)
|
if(items != null)
|
||||||
{
|
{
|
||||||
foreach(var datasetName in items.Keys)
|
foreach(var item in items)
|
||||||
{
|
{
|
||||||
var dataset = BuildDataset(context, fields, items[datasetName], auth);
|
var dataset = BuildDataset(context, fields, item);
|
||||||
if(dataset != null)
|
if(dataset != null)
|
||||||
{
|
{
|
||||||
responseBuilder.AddDataset(dataset);
|
responseBuilder.AddDataset(dataset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = new SaveInfo.Builder(fields.SaveType, fields.AutofillIds.ToArray()).Build();
|
|
||||||
responseBuilder.SetSaveInfo(info);
|
|
||||||
return responseBuilder.Build();
|
return responseBuilder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dataset BuildDataset(Context context, FieldCollection fields, IFilledItem filledItem, bool auth)
|
public static Dataset BuildDataset(Context context, FieldCollection fields, IFilledItem filledItem)
|
||||||
{
|
{
|
||||||
Dataset.Builder datasetBuilder;
|
var datasetBuilder = new Dataset.Builder(
|
||||||
if(auth)
|
|
||||||
{
|
|
||||||
datasetBuilder = new Dataset.Builder(
|
|
||||||
BuildListView(context.PackageName, filledItem.Name, filledItem.Subtitle, Resource.Drawable.fa_lock));
|
|
||||||
//IntentSender sender = AuthActivity.getAuthIntentSenderForDataset(context, datasetName);
|
|
||||||
//datasetBuilder.SetAuthentication(sender);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
datasetBuilder = new Dataset.Builder(
|
|
||||||
BuildListView(context.PackageName, filledItem.Name, filledItem.Subtitle, filledItem.Icon));
|
BuildListView(context.PackageName, filledItem.Name, filledItem.Subtitle, filledItem.Icon));
|
||||||
}
|
|
||||||
|
|
||||||
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
||||||
{
|
{
|
||||||
return datasetBuilder.Build();
|
return datasetBuilder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FillResponse BuildAuthResponse(Context context, FieldCollection fields)
|
||||||
|
{
|
||||||
|
var responseBuilder = new FillResponse.Builder();
|
||||||
|
var view = BuildListView(context.PackageName, "Autofill with bitwarden",
|
||||||
|
"Vault locked", Resource.Drawable.icon);
|
||||||
|
var intent = new Intent(context, typeof(AuthActivity));
|
||||||
|
var pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.CancelCurrent);
|
||||||
|
responseBuilder.SetAuthentication(fields.AutofillIds.ToArray(), pendingIntent.IntentSender, view);
|
||||||
|
return responseBuilder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
public static RemoteViews BuildListView(string packageName, string text, string subtext, int iconId)
|
public static RemoteViews BuildListView(string packageName, string text, string subtext, int iconId)
|
||||||
{
|
{
|
||||||
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Android;
|
using Android;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Service.Autofill;
|
using Android.Service.Autofill;
|
||||||
|
@ -18,6 +19,7 @@ namespace Bit.Android.Autofill
|
||||||
public class AutofillService : global::Android.Service.Autofill.AutofillService
|
public class AutofillService : global::Android.Service.Autofill.AutofillService
|
||||||
{
|
{
|
||||||
private ICipherService _cipherService;
|
private ICipherService _cipherService;
|
||||||
|
private ILockService _lockService;
|
||||||
|
|
||||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)
|
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)
|
||||||
{
|
{
|
||||||
|
@ -27,8 +29,6 @@ namespace Bit.Android.Autofill
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientState = request.ClientState;
|
|
||||||
|
|
||||||
var parser = new Parser(structure);
|
var parser = new Parser(structure);
|
||||||
parser.ParseForFill();
|
parser.ParseForFill();
|
||||||
|
|
||||||
|
@ -37,30 +37,31 @@ namespace Bit.Android.Autofill
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(_lockService == null)
|
||||||
|
{
|
||||||
|
_lockService = Resolver.Resolve<ILockService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(true) // if locked
|
||||||
|
{
|
||||||
|
var authResponse = AutofillHelpers.BuildAuthResponse(this, parser.FieldCollection);
|
||||||
|
callback.OnSuccess(authResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(_cipherService == null)
|
if(_cipherService == null)
|
||||||
{
|
{
|
||||||
_cipherService = Resolver.Resolve<ICipherService>();
|
_cipherService = Resolver.Resolve<ICipherService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// build response
|
// build response
|
||||||
var items = new Dictionary<string, IFilledItem>();
|
var items = await AutofillHelpers.GetFillItemsAsync(_cipherService, parser.Uri);
|
||||||
var ciphers = await _cipherService.GetAllAsync(parser.Uri);
|
|
||||||
if(ciphers.Item1.Any() || ciphers.Item2.Any())
|
|
||||||
{
|
|
||||||
var allCiphers = ciphers.Item1.ToList();
|
|
||||||
allCiphers.AddRange(ciphers.Item2.ToList());
|
|
||||||
foreach(var cipher in allCiphers)
|
|
||||||
{
|
|
||||||
items.Add(cipher.Id, new CipherFilledItem(cipher));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!items.Any())
|
if(!items.Any())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = AutofillHelpers.BuildFillResponse(this, false, parser.FieldCollection, items);
|
var response = AutofillHelpers.BuildFillResponse(this, parser.FieldCollection, items);
|
||||||
callback.OnSuccess(response);
|
callback.OnSuccess(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1373
src/Android/Resources/Resource.Designer.cs
generated
1373
src/Android/Resources/Resource.Designer.cs
generated
File diff suppressed because it is too large
Load diff
71
src/Android/Resources/layout/autofill_authactivity.axml
Normal file
71
src/Android/Resources/layout/autofill_authactivity.axml
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/authLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:importantForAutofill="noExcludeDescendants"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/master_login_header"
|
||||||
|
style="@style/TextAppearance.AppCompat.Large"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/password_label"
|
||||||
|
style="@style/TextAppearance.AppCompat.Body1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:labelFor="@+id/master_password"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/master_password"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/master_login_header" />
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/master_password"
|
||||||
|
android:layout_width="250sp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/password_label"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/password_label"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/password_label" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/cancel"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textColor="@android:color/holo_blue_dark"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/login"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/master_password" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/login"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textColor="@android:color/holo_blue_dark"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/cancel"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/cancel" />
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
|
@ -71,6 +71,8 @@
|
||||||
<package id="Xam.Plugin.Connectivity" version="3.0.2" targetFramework="monoandroid71" />
|
<package id="Xam.Plugin.Connectivity" version="3.0.2" targetFramework="monoandroid71" />
|
||||||
<package id="Xam.Plugins.Settings" version="3.0.1" targetFramework="monoandroid71" />
|
<package id="Xam.Plugins.Settings" version="3.0.1" targetFramework="monoandroid71" />
|
||||||
<package id="Xamarin.Android.Support.Animated.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
|
<package id="Xamarin.Android.Support.Animated.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
|
||||||
|
<package id="Xamarin.Android.Support.Constraint.Layout" version="1.0.2.2" targetFramework="monoandroid80" />
|
||||||
|
<package id="Xamarin.Android.Support.Constraint.Layout.Solver" version="1.0.2.2" targetFramework="monoandroid80" />
|
||||||
<package id="Xamarin.Android.Support.Design" version="23.3.0" targetFramework="monoandroid60" />
|
<package id="Xamarin.Android.Support.Design" version="23.3.0" targetFramework="monoandroid60" />
|
||||||
<package id="Xamarin.Android.Support.v4" version="23.3.0" targetFramework="monoandroid60" />
|
<package id="Xamarin.Android.Support.v4" version="23.3.0" targetFramework="monoandroid60" />
|
||||||
<package id="Xamarin.Android.Support.v7.AppCompat" version="23.3.0" targetFramework="monoandroid60" />
|
<package id="Xamarin.Android.Support.v7.AppCompat" version="23.3.0" targetFramework="monoandroid60" />
|
||||||
|
|
Loading…
Reference in a new issue