Jump to content

Dave Nottage

Members
  • Content Count

    1331
  • Joined

  • Last visited

  • Days Won

    29

Posts posted by Dave Nottage


  1. 4 minutes ago, Chester Wilson said:

    You are the first person to give me some kind of helpful advice for this problem, and I appreciate that greatly!

    You're welcome!

    4 minutes ago, Chester Wilson said:

    At least I get a different error message now: "Install Parse Failed No Certificates".  I don't know what that means

    I'm pretty sure that means that there's an existing app on the device where the certificate does not match the one of the app replacing it. This can be caused by using a new version of Delphi (or one from a different machine) to deploy the app, because the debug.keystore file in the %APPDATA%\Embarcadero\BDS\xx.x folder (where xx.x is 20.0, for Delphi 10.3) is different. One trick to fixing this is to use the debug.keystore file from the previous version, or just delete the app from the device before redeploying.

    9 minutes ago, Chester Wilson said:

    Is this going to happen to all devices with future releases of Delphi, so we have to keep an old version of Delphi around so we can still service clients with older machines?

    Are you referring to the minSdkVersion issue? It'll happen whenever they decide to change the minimum supported version. The Android OS does not help in this regard when the device spits out a cryptic error message instead of saying "the minSdkVersion value is too high for this device"

    13 minutes ago, Chester Wilson said:

    Or every so often do we have to tell people to go out and buy new machines?

    Google themselves already encourage people to upgrade their devices, because supporting a growing number of older versions is time consuming and costly.

    • Thanks 1

  2. 1 hour ago, Fabian1648 said:

    What would be the best approach to solve this problem?

    One way would be to use something other than the camera component in Delphi. Although it may need some work, you could try my camera project: 

     

      https://github.com/DelphiWorlds/Camera

     

    I am reviving the project, however that may be some time away, so you might want to consider something else, like WinSoft's:

     

      https://www.winsoft.sk/acamera.htm


  3. 7 hours ago, Rollo62 said:

    I found some API to customize UserNotifications UI, not looked too deep into it, but maybe that could do what I want.

    Oddly enough, I started looking at that recently, to include images in push notifications (which is way easier on Android). Unfortunately, it requires an iOS app extension which you have to create with Xcode (not real hard to do, mind you). I have shelved it for the time being because I have not been able to make it work as yet. I hope to come back to it in the next few days.

    • Like 1

  4. After a lot of Googling and experimenting, I managed to solve this myself.

     

    From what I could gather, and this may apply only to later versions of Android (I have Android 10 on my Pixel 3a), the method that FMX uses to "scan" the image file into the gallery, which is via an intent (source\rtl\androiddex\java\fmx\src\com\embarcadero\firemonkey\medialibrary\MediaImage.java):

        /**
         * Adds photo to Gallery application.
         */
        public void addPhotoToGallery() {
            Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            intent.setData(getFileUri());
    
            activity.sendBroadcast(intent);
        }

    Does not immediately update the media "database". I discovered that using the scanFile method of the MediaScannerConnection class:

     

      https://developer.android.com/reference/android/media/MediaScannerConnection

     

    Does cause an immediate update, so that requerying using ContentResolver now works. I used the code in this article:

     

      https://www.grokkingandroid.com/adding-files-to-androids-media-library-using-the-mediascanner/

     

    To come up with the additional Delphi code required, which is in the attached test app. I've added some comments which should clarify the whys and hows.

     

     

    AndroidGetPhotoV2.zip


  5. I'm having an issue with being able to retrieve photos from the camera roll when using TTakePhotoFromCameraAction. It's probably not a bug; it's more likely a case of "yet to be implemented" 🙂

     

    I've attached a project that demonstrates the issue. On startup the test app requests permission to read external storage, then if granted proceeds to retrieve the photos from the camera roll. This works fine.

     

    When the TakePhoto button is clicked, the app requests permissions for the camera and to write to external storage, then proceeds to execute the action. When the user has finished taking the photo, the TakePhotoFromCameraActionDidFinishTaking event fires, and the app reloads the photos from the camera roll. One would expect the newly taken photo to be in the roll, however it is not.

     

    Restarting the app still does not show the new photo. Running the Photos app on the device shows the new photo, however with a slight delay before it actually appears - I'm not sure if this is a clue. Restart the test app again, et voila! The new photo is there.

     

    The question is: why does the photo not appear when restarting the app (not that I'd expect anyone to do this), and not until the Photos app has been run?

     

     

    AndroidGetPhoto.zip


  6. 2 hours ago, Dave Nottage said:

    I'll take a look at your example today and see what the problem might be.

    I should have realised from the beginning - it's a method resolution problem. The Objective-C method name is actually setCategory, not setCategoryWithOptionsError, so it needs a MethodName attribute. This executes without error for me:

     

    uses
      Macapi.ObjectiveC, Macapi.Helpers,
      iOSapi.Foundation, iOSapi.AVFoundation, iOSapi.CocoaTypes;
    
    const
      AVAudioSessionCategoryOptionNone = 0;
      AVAudioSessionCategoryOptionMixWithOthers = 1;
      AVAudioSessionCategoryOptionDuckOthers = 2;
      AVAudioSessionCategoryOptionAllowBluetooth = 4;
      AVAudioSessionCategoryOptionDefaultToSpeaker = 8;
      AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers = 17;
      AVAudioSessionCategoryOptionAllowBluetoothA2DP = 32;
      AVAudioSessionCategoryOptionAllowAirPlay = 64;
    
    type
      AVAudioSessionCategory = NSString;
      AVAudioSessionCategoryOptions = NSInteger;
    
      AVAudioSessionClass = interface(NSObjectClass)
        ['{F8B0F7A3-1805-4739-B827-3AE7FFCD20F0}']
        {class} function sharedInstance: Pointer; cdecl;
      end;
      AVAudioSession = interface(NSObject)
        ['{C6EAD2A6-DE66-4A80-B019-8564FF6927AF}']
        function category: NSString; cdecl;
        function currentHardwareInputNumberOfChannels: NSInteger; cdecl;
        function currentHardwareOutputNumberOfChannels: NSInteger; cdecl;
        function currentHardwareSampleRate: double; cdecl;
        function delegate: Pointer; cdecl;
        function inputIsAvailable: Boolean; cdecl;
        function mode: NSString; cdecl;
        function preferredHardwareSampleRate: double; cdecl;
        function preferredIOBufferDuration: NSTimeInterval; cdecl;
        function setActive(beActive: Boolean; error: PPointer): Boolean; cdecl; overload;
        function setActive(beActive: Boolean; withFlags: NSInteger; error: PPointer): Boolean; cdecl; overload;
        function setCategory(theCategory: NSString; error: PPointer): Boolean; cdecl;
        [MethodName('setCategory:withOptions:error:')]
        function setCategoryWithOptionsError(category: AVAudioSessionCategory; withOptions: AVAudioSessionCategoryOptions; error: PPointer): Boolean; cdecl;
        procedure setDelegate(delegate: Pointer); cdecl;
        function setMode(theMode: NSString; error: PPointer): Boolean; cdecl;
        function setPreferredHardwareSampleRate(sampleRate: double; error: PPointer): Boolean; cdecl;
        function setPreferredIOBufferDuration(duration: NSTimeInterval; error: PPointer): Boolean; cdecl;
      end;
      TAVAudioSession = class(TOCGenericImport<AVAudioSessionClass, AVAudioSession>)  end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      LSession: AVAudioSession;
      LErrorPtr: Pointer;
    begin
      LErrorPtr := nil;
      LSession := TAVAudioSession.Wrap(TAVAudioSession.OCClass.sharedInstance);
      if LSession.setCategoryWithOptionsError(AVAudioSessionCategoryPlayback, AVAudioSessionCategoryOptionNone, @LErrorPtr) then
        Label1.Text := 'Set category OK'
      else if LErrorPtr <> nil then
        Label1.Text := NSStrToStr(TNSError.Wrap(LErrorPtr).localizedDescription);
    end;

     


  7. 7 hours ago, Rollo62 said:

    I hope this is ending the long, long favorized age of "SORRY, I can't say anything, because of RadStudio Beta NDA will kill me and eat my soul".

    As Bill said, it's a "we've been given permission" thing. This is not new, however - I think it has been a "thing" for at least a couple of major releases now. Also, you missed my post:

     

    https://www.delphiworlds.com/2020/05/its-time-to-get-excited/

     

    🙂

     

    • Thanks 1

  8. 13 hours ago, Rollo62 said:

    Every time I see a NSError now I will have to replace it bei PPointer, what a mess

    Every time you see a NSError** you'll need to replace it with PPointer. Also, if you see:

    (NSError * _Nullable * _Nullable)

    (Which amounts to the same thing)

    12 hours ago, Rollo62 said:

    Or did I misunderstand your post, and you say this is currently under 10.3.3 NOT usable, even with the proposals you gave,

    I was just pointing out the incorrect declaration; I have not looked at any other aspect as yet. I'll take a look at your example today and see what the problem might be.


  9. 1 hour ago, Rollo62 said:

    There is a prototype in the iOSapi_Foundation.pas file already, the setCategory

    I think you mean in iOSapi.AVFoundation.pas. You're working under the assumption that the declaration is correct - it is not.

     

    1 hour ago, Rollo62 said:

    Why should it be error: PPointer, right now ?

    Because the corresponding declaration in Objective-C is:

    - (BOOL)setCategory:(AVAudioSessionCategory)category error:(NSError **)outError

    Note the double asterisk. Because the Objective-C bridge is presently unable to handle wrapping this, a PPointer can be used in the way I described in my reply. As an example, see the code for the DownloadableContentPath method in FMX.InAppPurchase.iOS, which uses the setResourceValue method of NSURL, which is declared correctly.

     

    1 hour ago, Rollo62 said:

    You mean feature request for the iOSapi_Foundation, right ?

    It's a QP report either way, however I'm referring to the missing declaration for the setCategoryWithOptionsError method of AVAudioSession (and any others methods that might be needed) in iOSapi.AVFoundation.

    1 hour ago, Rollo62 said:

    Since I assume those are copied and get actually from the current system,

    No, that's not automatic.

    1 hour ago, Rollo62 said:

    this should always up to date

    It would be nice if it were, however translating SDK framework headers is not trivial, and as you've observed declaration errors can happen, and/or the Objective-C bridge needs to be modified to handle situations where needed. Up until 10.3.2 (or somewhere close to that), the bridge could not even handle Objective-C blocks on macOS (it could on iOS).


  10. 4 hours ago, Rollo62 said:

    function setCategoryWithOptionsError( category: NSString; withOptions : AVAudioSessionCategoryOptions; error: NSError😞 Boolean; cdecl;

     

    It should be:

    function setCategoryWithOptionsError(category: NSString; withOptions: AVAudioSessionCategoryOptions; error: PPointer): Boolean; cdecl;

    But you don't show how you're calling it, either. A possible example:

    var
      LErrorPtr: Pointer;
      LError: NSError;
      
     ...
     
       setCategoryWithOptionsError(CocoaNSStringConst(libAVFoundation, 'AVAudioSessionCategoryPlayAndRecord'), 0, @LErrorPtr);
       if LErrorPtr <> nil then
       begin
         LError := TNSError.Wrap(LErrorPtr);
         // Deal with LError here
       end;

     

    4 hours ago, Rollo62 said:

    Shall I use different GUID for the interfaces,

    You should.

     

    4 hours ago, Rollo62 said:

    Is the new method not yet in the Rx10.3.3  libraries,

    No. I suggest filing a QP report.

    • Like 1

  11. 3 hours ago, Rollo62 said:

    The devices send new datastring every < 2 sec.

    My first guess would be that the data rate is too fast for too long, and iOS kills your app. Before I dive headlong into a deeper look, you might want to read this:

     

    https://stackoverflow.com/a/34122794/3164070

     

    ..which links to:

     

    https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html


  12.  

    13 hours ago, Rollo62 said:

    Other ways to construct this NSDictionary look to ugly to be true:

    They're ugly, but they are true. In your case however you should need only use dictionaryWithObject (since there is only one value), thus:

    var
      LDict: NSDictionary;
      LBooleanValue: Pointer;
    begin
      LBooleanValue := TNSNumber.OCClass.numberWithBool(True);
      LDict := TNSDictionary.Wrap(TNSDictionary.OCClass.dictionaryWithObject(LBooleanValue, NSObjectToID(CBCentralManagerOptionShowPowerAlertKey)));
    end;

     

    • Thanks 1
×