@@ -935,13 +935,86 @@ void AddBinaryOrAsk(Uri filename)
935935 AddBinary ( filename , true ) ;
936936 }
937937
938+ /// <summary>
939+ /// Variant of <see cref="AddBinaryOrAsk"/> for bytes that are already in memory
940+ /// (e.g. an in-app camera capture). Shows the same overwrite/rename dialog.
941+ /// </summary>
942+ void AddBinaryFromBytesOrAsk ( string filename , byte [ ] bytes )
943+ {
944+ if ( State . Entry . Binaries . Get ( filename ) != null )
945+ {
946+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder ( this ) ;
947+ builder . SetTitle ( GetString ( Resource . String . AskOverwriteBinary_title ) ) ;
948+ builder . SetMessage ( GetString ( Resource . String . AskOverwriteBinary ) ) ;
949+ builder . SetPositiveButton ( GetString ( Resource . String . AskOverwriteBinary_yes ) , ( dlgSender , dlgEvt ) =>
950+ {
951+ AddBinaryFromBytes ( filename , bytes , true ) ;
952+ } ) ;
953+ builder . SetNegativeButton ( GetString ( Resource . String . AskOverwriteBinary_no ) , ( dlgSender , dlgEvt ) =>
954+ {
955+ AddBinaryFromBytes ( filename , bytes , false ) ;
956+ } ) ;
957+ builder . SetNeutralButton ( GetString ( Android . Resource . String . Cancel ) , ( dlgSender , dlgEvt ) => { } ) ;
958+ builder . Create ( ) . Show ( ) ;
959+ }
960+ else
961+ {
962+ AddBinaryFromBytes ( filename , bytes , true ) ;
963+ }
964+ }
965+
966+ /// <summary>
967+ /// Stores <paramref name="bytes"/> directly as a <see cref="ProtectedBinary"/> attachment.
968+ /// When <paramref name="overwrite"/> is false the filename is made unique by appending a counter.
969+ /// </summary>
970+ void AddBinaryFromBytes ( string filename , byte [ ] bytes , bool overwrite )
971+ {
972+ string strItem = filename ;
973+ if ( ! overwrite )
974+ {
975+ string strBase = KeePassLib . Utility . UrlUtil . StripExtension ( strItem ) ;
976+ string strExt = "." + KeePassLib . Utility . UrlUtil . GetExtension ( strItem ) ;
977+ int nTry = 0 ;
978+ while ( true )
979+ {
980+ string strNewName = strBase + nTry . ToString ( System . Globalization . CultureInfo . InvariantCulture ) + strExt ;
981+ if ( State . Entry . Binaries . Get ( strNewName ) == null )
982+ {
983+ strItem = strNewName ;
984+ break ;
985+ }
986+ ++ nTry ;
987+ }
988+ }
989+ try
990+ {
991+ State . Entry . Binaries . Set ( strItem , new KeePassLib . Security . ProtectedBinary ( false , bytes ) ) ;
992+ }
993+ catch ( Exception exAttach )
994+ {
995+ App . Kp2a . ShowMessage ( this ,
996+ GetString ( Resource . String . AttachFailed ) + " " + Util . GetErrorMessage ( exAttach ) ,
997+ MessageSeverity . Error ) ;
998+ }
999+ State . EntryModified = true ;
1000+ PopulateBinaries ( ) ;
1001+ }
1002+
9381003 protected override void OnResume ( )
9391004 {
9401005 if ( _uriToAddOrAsk != null )
9411006 {
9421007 AddBinaryOrAsk ( _uriToAddOrAsk ) ;
9431008 _uriToAddOrAsk = null ;
9441009 }
1010+ if ( _cameraPhotoBytes != null )
1011+ {
1012+ var bytes = _cameraPhotoBytes ;
1013+ var filename = _cameraPhotoFilename ?? "photo.jpg" ;
1014+ _cameraPhotoBytes = null ;
1015+ _cameraPhotoFilename = null ;
1016+ AddBinaryFromBytesOrAsk ( filename , bytes ) ;
1017+ }
9451018 base . OnResume ( ) ;
9461019 }
9471020
@@ -1401,6 +1474,17 @@ protected override void OnActivityResult(int requestCode, Result resultCode, Int
14011474 }
14021475 _uriToAddOrAsk = uri ; //we can't launch a dialog in onActivityResult, so delay this to onResume
14031476 }
1477+ else if ( requestCode == Intents . RequestCodeCameraCapture )
1478+ {
1479+ // Transfer bytes via static field – Intent extras have a 1 MB IPC limit.
1480+ var photoBytes = CapturePhotoActivity . CapturedPhotoBytes ;
1481+ CapturePhotoActivity . CapturedPhotoBytes = null ;
1482+ if ( photoBytes != null )
1483+ {
1484+ _cameraPhotoBytes = photoBytes ;
1485+ _cameraPhotoFilename = data ? . GetStringExtra ( CapturePhotoActivity . ExtraFilename ) ?? "photo.jpg" ;
1486+ }
1487+ }
14041488 return ;
14051489 case Result . Canceled :
14061490 return ;
@@ -1466,8 +1550,29 @@ void PopulateBinaries()
14661550 addBinaryButton . Enabled = ! State . Entry . Binaries . Any ( ) ;
14671551 addBinaryButton . Click += ( sender , e ) =>
14681552 {
1469- Util . ShowBrowseDialog ( this , Intents . RequestCodeFileBrowseForBinary , false , true /*force OpenDocument if available, GetContent is not well support starting with Android 7 */ ) ;
1470-
1553+ // Offer both a file browser and an in-app camera (no disk/gallery write).
1554+ var items = new string [ ]
1555+ {
1556+ GetString ( Resource . String . attach_browse_file ) ,
1557+ GetString ( Resource . String . attach_take_photo )
1558+ } ;
1559+ new Google . Android . Material . Dialog . MaterialAlertDialogBuilder ( this )
1560+ . SetTitle ( Resource . String . add_binary )
1561+ . SetItems ( items , ( dlgSender , dlgArgs ) =>
1562+ {
1563+ if ( dlgArgs . Which == 0 )
1564+ {
1565+ Util . ShowBrowseDialog ( this , Intents . RequestCodeFileBrowseForBinary , false ,
1566+ true /*force OpenDocument if available*/ ) ;
1567+ }
1568+ else
1569+ {
1570+ StartActivityForResult (
1571+ new Intent ( this , typeof ( CapturePhotoActivity ) ) ,
1572+ Intents . RequestCodeCameraCapture ) ;
1573+ }
1574+ } )
1575+ . Show ( ) ;
14711576 } ;
14721577
14731578 binariesGroup . AddView ( addBinaryButton , layoutParams ) ;
@@ -1581,6 +1686,8 @@ ExtraEditView CreateExtraStringEditView(KeyValuePair<string, ProtectedString> pa
15811686 private string [ ] _additionalKeys = null ;
15821687 private List < View > _editModeHiddenViews ;
15831688 private Uri _uriToAddOrAsk ;
1689+ private byte [ ] ? _cameraPhotoBytes ;
1690+ private string ? _cameraPhotoFilename ;
15841691
15851692 public string [ ] AdditionalKeys
15861693 {
0 commit comments