Howto disable A2DP on the iPhone

UPDATE [October 4, 2009]: I have created a utility that prevents the manual labour and can be installed via Cydia.

UPDATE [August 20, 2009]: The previous post described a method that didn’t work well. Shawn Porter pointed out the problem, so I have updated the post and now it works fine.

I have an iPhone 3G and use it in my car as a music player. I have installed an GROM audio adapter for the iPhone, so I can connect it to my Mazda 6 car stereo. The GROM adapter even allows to use the controls on the steering wheel. Everything worked just fine, until iPhone OS 3.0 came along.

iPhone OS 3.0 includes support for A2DP, which is capable of stream audio via BlueTooth to another device. Unfortunately, there is no way to disable A2DP and it will always select the A2DP source when it is available. You can switch back to the dock connector, but you need to do this each time you start your car.

Apple should make an option to select which BlueTooth profiles should be enabled for a certain connection, but this is not possible with the current firmware. I decided to jailbreak my iPhone and try to find a solution.

The interesting directory is /var/mobile/Library/Preferences, where the iPhone stores the BlueTooth settings. These settings are stored in the PLIST format, so you need a PLIST editor (Mac or PC) or you need to convert them by hand. Transfer the com.apple.MobileBluetooth.services.plist to your computer (refer to Simon’s blog to find out how to do this). You can also use iFile to edit the file on your iPhone directly (thanks to Richard van den Berg for pointing it out).

Open the com.apple.MobileBluetooth.services.plist in your PLIST editor and make sure you edit the A2DPService section and store the devices you don’t want to use A2DP in the UnauthorizeList (without the letter “d”). This dictionary will probably not exist yet, so you need to add it by hand. You need to know the MAC address, but it is probably listed already in this file.

<key>A2DPService</key>
<dict>
  <key>State</key>
  <true/>
  <key>UnauthorizeList</key>
  <dict>
    <key>00:10:60:D0:91:D0</key>        <!-- This is the MAC address -->
    <date>2009-08-08T01:00:00Z</date>   <!-- Timestamp -->
  </dict>
</dict>

Transfer the PLIST file back to your iPhone and reboot the device. Once it gets back on, then the A2DP service should be disabled.

TextBlock.GetPositionFromPoint raises NullReferenceException

The TextBlock.GetPositionFromPoint raises a NullReferenceException in some cases. This method should return a TextPointer to the text that starts at the given position. In some cases it generates a NullReferenceException instead, which is clearly a bug. The internals of this method looks like:

public TextPointer GetPositionFromPoint(Point point, bool snapToText)
{
  if (this.CheckFlags(Flags.ContentChangeInProgress))
    throw new InvalidOperationException(SR.Get("TextContainerChangingReentrancyInvalid"));

  if (((ITextView)this._complexContent.TextView).Validate(point))
    return (TextPointer) this._complexContent.TextView.GetTextPositionFromPoint(point, snapToText);

  if (snapToText)
    return new TextPointer((TextPointer) this._complexContent.TextContainer.Start);

  return null;
}

The call fails in the TextBlock.GetPositionFromPoint according to the call-stack. The most obvious error is that the _complexContent field isn’t set. After some research, I found out that most methods that rely on this field call TextBlock.EnsureComplexContent() first. This has been neglected in the TextBlock.GetPositionFromPoint method. We cannot call TextBlock.EnsureComplexContent from usercode, because it is a private method. You can get the ContentStart or ContentEnd properties, which will call TextBlock.EnsureComplexContent.

...
var dummy = this.myTextBox.ContentStart;   // WORKAROUND
TextPointer start = this.myTextBox.GetPositionFromPoint(selection.TopLeft, true);
...

Conclusion
This bug should be fixed by Microsoft by adding a call to EnsureComplexContent before it accesses the _complexContent field. It’s fairly easy to prevent the exception, but it might have took you some time to find out about the error and get to this blog.

Response from Microsoft
I have submitted this bug to Microsoft and it is filed under feedback item #478575. Microsoft confirmed it is a bug and it will be fixed when .NET Framework 4 will hit RTM (the current beta still has the bug).

Sample application
Download the sample application.

ScrollViewer always handles the mousewheel

The ScrollViewer control is a convenient control, when the content doesn’t fit on screen and you need some kind of scrolling to make all content accessible. I tend to use the mousewheel to scroll up and down, but this sometimes doesn’t work in WPF applications. It fails when you nest multiple scrollviewers, because the deepest ScrollViewer will always handle the MouseWheel message. Even if there is no vertical scrollbar visible or when it is disabled.

You can see this behaviour when you paste the following code in XamlPad. When you scroll down using the mousewheel and you hit the white area, then it is impossible to scroll further using the mousewheel.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Height="400">
 <ScrollViewer VerticalScrollBarVisibility="Auto">
  <StackPanel>
    <Border BorderBrush="Black" BorderThickness="1" Width="400" Height="200" Background="Red"/>
    <ScrollViewer VerticalScrollBarVisibility="Disabled">
      <Border BorderBrush="Black" BorderThickness="1" Width="400" Height="200" Background="White"/>
    </ScrollViewer>
    <Border BorderBrush="Black" BorderThickness="1" Width="400" Height="200" Background="Blue"/>
  </StackPanel>
</ScrollViewer>
</Page>

It’s a bad idea to have two vertical scrollbars that are nested, but sometimes you use a control that has a ScrollViewer inside its template. Because you have wrapped the control in your own ScrollViewer, the scrollbars will never be visible. But because it does handle the MouseWheel messages, the mousewheel will stop scrolling when the mouse hovers over it. This is very annoying to the end-user.

I used .NET Reflector to check how the MouseWheel message was handled. It looks like this:

protected override void OnMouseWheel(MouseWheelEventArgs e)
{
  if (!e.Handled && this.HandlesMouseWheelScrolling)
  {
    if (this.ScrollInfo != null)
    {
      if (e.Delta < 0)
        this.ScrollInfo.MouseWheelDown();
      else
        this.ScrollInfo.MouseWheelUp();
    }
    e.Handled = true;
  }
}

As you can see, it’s quite easy to prevent the MouseWheel message from being processed and that is to set the HandlesMouseWheelScrolling to false, when the vertical scrollbar is hidden or invisible. Unfortunately, Microsoft decided to mark this property as internal, so it isn’t accessible. This property is used exclusively by the TextBox control (it uses a ScrollViewer too), that disables the handling of the MouseWheel message of the internal ScrollViewer. The result is that a TextBox doesn’t block vertical scrolling when using the mousewheel.

The problem can also be solved a different way, but this requires is us to subclass the ScrollViewer:

public class ExtScrollViewer : ScrollViewer
{
  private ScrollBar verticalScrollbar;

  public override void OnApplyTemplate()
  {
    // Call base class
    base.OnApplyTemplate();

    // Obtain the vertical scrollbar
    this.verticalScrollbar = this.GetTemplateChild("PART_VerticalScrollBar") as ScrollBar;
  }

  protected override void OnMouseWheel(MouseWheelEventArgs e)
  {
    // Only handle this message if the vertical scrollbar is in use
    if ((this.verticalScrollbar != null) && (this.verticalScrollbar.Visibility == Visibility.Visible) && this.verticalScrollbar.IsEnabled)
    {
      // Perform default handling
      base.OnMouseWheel(e);
    }
  }
}

The new MouseWheel handling doesn’t do anything when the vertical scrollbar is either invisible or disabled. The drawback of this approach is that you need to use a custom ScrollViewer, instead of the default ScrollViewer. You can do this pretty easy for your own controls, but if you have third party controls that you don’t control, then this won’t work.

Sebastien Lambla describes a method on his blog that allows you to change the characteristics of the default ScrollViewer. Use whatever you like best.

I have submitted this bug with Microsoft. You can track it here.