Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 876dd208 authored by Chen Chen's avatar Chen Chen Committed by Gerrit Code Review
Browse files

Merge "UpdateAbility: Implement BluetoothInCallService and its test. Besides...

Merge "UpdateAbility: Implement BluetoothInCallService and its test. Besides atest, also manually tested hfp."
parents b70f7144 6a8cbac5
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
    <uses-permission android:name="android.permission.BLUETOOTH_MAP"/>
    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
    <uses-permission android:name="android.permission.DUMP"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@@ -424,5 +425,17 @@
            <meta-data android:name="android.accounts.AccountAuthenticator"
                 android:resource="@xml/authenticator"/>
        </service>
        <service
            android:name=".hfp.BluetoothInCallService"
            android:permission="android.permission.BIND_INCALL_SERVICE"
            android:process="@string/process"
            android:enabled="@bool/profile_supported_hfp_incallservice"
            android:exported="true">
            <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING"
                       android:value="true" />
            <intent-filter>
              <action android:name="android.telecom.InCallService"/>
            </intent-filter>
         </service>
    </application>
</manifest>
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
    <bool name="profile_supported_a2dp_sink">false</bool>
    <bool name="profile_supported_hs_hfp">true</bool>
    <bool name="profile_supported_hfpclient">false</bool>
    <bool name="profile_supported_hfp_incallservice">true</bool>
    <bool name="profile_supported_hid_host">true</bool>
    <bool name="profile_supported_opp">true</bool>
    <bool name="profile_supported_pan">true</bool>
+26 −0
Original line number Diff line number Diff line
@@ -17,9 +17,13 @@
package com.android.bluetooth.btservice;

import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.os.Message;
import android.util.Log;

import com.android.bluetooth.R;
import com.android.bluetooth.hfp.BluetoothInCallService;
import com.android.bluetooth.statemachine.State;
import com.android.bluetooth.statemachine.StateMachine;

@@ -75,6 +79,10 @@ final class AdapterState extends StateMachine {
    static final int BREDR_START_TIMEOUT_DELAY = 4000;
    static final int BREDR_STOP_TIMEOUT_DELAY = 4000;

    static final ComponentName BLUETOOTH_INCALLSERVICE_COMPONENT
            = new ComponentName(R.class.getPackage().getName(),
            BluetoothInCallService.class.getCanonicalName());

    private AdapterService mAdapterService;
    private TurningOnState mTurningOnState = new TurningOnState();
    private TurningBleOnState mTurningBleOnState = new TurningBleOnState();
@@ -222,6 +230,24 @@ final class AdapterState extends StateMachine {
            return BluetoothAdapter.STATE_ON;
        }

        @Override
        public void enter() {
            super.enter();
            mAdapterService.getPackageManager().setComponentEnabledSetting(
                    BLUETOOTH_INCALLSERVICE_COMPONENT,
                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                    PackageManager.DONT_KILL_APP);
        }

        @Override
        public void exit() {
            mAdapterService.getPackageManager().setComponentEnabledSetting(
                    BLUETOOTH_INCALLSERVICE_COMPONENT,
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
            super.exit();
        }

        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
+317 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth.hfp;

import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.telecom.Call;
import android.telecom.GatewayInfo;
import android.telecom.InCallService;
import android.telecom.PhoneAccountHandle;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;

/**
 * A proxy class of android.telecom.Call that
 * 1) facilitates testing of the BluetoothInCallService class; We can't mock the final class
 * Call directly;
 * 2) Some helper functions, to let Call have same methods as com.android.server.telecom.Call
 *
 * This is necessary due to the "final" attribute of the Call class. In order to
 * test the correct functioning of the BluetoothInCallService class, the final class must be put
 * into a container that can be mocked correctly.
 */
@VisibleForTesting
public class BluetoothCall {

    private Call mCall;

    public Call getCall() {
        return mCall;
    }

    public void setCall(Call call) {
        mCall = call;
    }

    public BluetoothCall(Call call) {
        mCall = call;
    }

    public String getRemainingPostDialSequence() {
        return mCall.getRemainingPostDialSequence();
    }

    public void answer(int videoState) {
        mCall.answer(videoState);
    }

    public void deflect(Uri address) {
        mCall.deflect(address);
    }

    public void reject(boolean rejectWithMessage, String textMessage) {
        mCall.reject(rejectWithMessage, textMessage);
    }

    public void disconnect() {
        mCall.disconnect();
    }

    public void hold() {
        mCall.hold();
    }

    public void unhold() {
        mCall.unhold();
    }

    public void enterBackgroundAudioProcessing() {
        mCall.enterBackgroundAudioProcessing();
    }

    public void exitBackgroundAudioProcessing(boolean shouldRing) {
        mCall.exitBackgroundAudioProcessing(shouldRing);
    }

    public void playDtmfTone(char digit) {
        mCall.playDtmfTone(digit);
    }

    public void stopDtmfTone() {
        mCall.stopDtmfTone();
    }

    public void postDialContinue(boolean proceed) {
        mCall.postDialContinue(proceed);
    }

    public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
        mCall.phoneAccountSelected(accountHandle, setDefault);
    }

    public void conference(BluetoothCall callToConferenceWith) {
        if (callToConferenceWith != null) {
            mCall.conference(callToConferenceWith.getCall());
        }
    }

    public void splitFromConference() {
        mCall.splitFromConference();
    }

    public void mergeConference() {
        mCall.mergeConference();
    }

    public void swapConference() {
        mCall.swapConference();
    }

    public void pullExternalCall() {
        mCall.pullExternalCall();
    }

    public void sendCallEvent(String event, Bundle extras) {
        mCall.sendCallEvent(event, extras);
    }

    public void sendRttRequest() {
        mCall.sendRttRequest();
    }

    public void respondToRttRequest(int id, boolean accept) {
        mCall.respondToRttRequest(id, accept);
    }

    public void handoverTo(PhoneAccountHandle toHandle, int videoState, Bundle extras) {
        mCall.handoverTo(toHandle, videoState, extras);
    }

    public void stopRtt() {
        mCall.stopRtt();
    }

    public void putExtras(Bundle extras) {
        mCall.putExtras(extras);
    }

    public void putExtra(String key, boolean value) {
        mCall.putExtra(key, value);
    }

    public void putExtra(String key, int value) {
        mCall.putExtra(key, value);
    }

    public void putExtra(String key, String value) {
        mCall.putExtra(key, value);
    }

    public void removeExtras(List<String> keys) {
        mCall.removeExtras(keys);
    }

    public void removeExtras(String... keys) {
        mCall.removeExtras(keys);
    }

    public String getParentId() {
        Call parent = mCall.getParent();
        if (parent != null) {
            return parent.getDetails().getTelecomCallId();
        }
        return null;
    }

    public List<String> getChildrenIds() {
        return getIds(mCall.getChildren());
    }

    public List<String> getConferenceableCalls() {
        return getIds(mCall.getConferenceableCalls());
    }

    public int getState() {
        return mCall.getState();
    }

    public List<String> getCannedTextResponses() {
        return mCall.getCannedTextResponses();
    }

    public InCallService.VideoCall getVideoCall() {
        return mCall.getVideoCall();
    }

    public Call.Details getDetails() {
        return mCall.getDetails();
    }

    public Call.RttCall getRttCall() {
        return mCall.getRttCall();
    }

    public boolean isRttActive() {
        return mCall.isRttActive();
    }

    public void registerCallback(Call.Callback callback) {
        mCall.registerCallback(callback);
    }

    public void registerCallback(Call.Callback callback, Handler handler) {
        mCall.registerCallback(callback, handler);
    }

    public void unregisterCallback(Call.Callback callback) {
        mCall.unregisterCallback(callback);
    }

    public String toString() {
        String string = mCall.toString();
        return string == null ? "" : string;
    }

    public void addListener(Call.Listener listener) {
        mCall.addListener(listener);
    }

    public void removeListener(Call.Listener listener) {
        mCall.removeListener(listener);
    }

    public String getGenericConferenceActiveChildCallId() {
        return mCall.getGenericConferenceActiveChildCall().getDetails().getTelecomCallId();
    }

    public String getContactDisplayName() {
        return mCall.getDetails().getContactDisplayName();
    }

    public PhoneAccountHandle getAccountHandle() {
        return mCall.getDetails().getAccountHandle();
    }

    public int getVideoState() {
        return mCall.getDetails().getVideoState();
    }

    public String getCallerDisplayName() {
        return mCall.getDetails().getCallerDisplayName();
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return getCall() == null;
        }
        return o instanceof BluetoothCall && getCall() == ((BluetoothCall) o).getCall();
    }

    // helper functions
    public boolean isSilentRingingRequested() {
        return getDetails().getExtras().getBoolean(Call.EXTRA_SILENT_RINGING_REQUESTED);
    }

    public boolean isConference() {
        return getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE);
    }

    public boolean can(int capability) {
        return getDetails().can(capability);
    }

    public Uri getHandle() {
        return getDetails().getHandle();
    }

    public GatewayInfo getGatewayInfo() {
        return getDetails().getGatewayInfo();
    }

    public boolean isIncoming() {
        return getDetails().getCallDirection() == Call.Details.DIRECTION_INCOMING;
    }

    public boolean isExternalCall() {
        return getDetails().hasProperty(Call.Details.PROPERTY_IS_EXTERNAL_CALL);
    }

    public String getTelecomCallId() {
        return getDetails().getTelecomCallId();
    }

    public boolean wasConferencePreviouslyMerged() {
        return can(Call.Details.CAPABILITY_SWAP_CONFERENCE) &&
                !can(Call.Details.CAPABILITY_MERGE_CONFERENCE);
    }

    public static List<String> getIds(List<Call> calls) {
        List<String> result = new ArrayList<>();
        for (Call call : calls) {
            if (call != null) {
                result.add(call.getDetails().getTelecomCallId());
            }
        }
        return result;
    }
}
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth.hfp;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;

import java.util.List;

/**
 * A proxy class that facilitates testing of the BluetoothInCallService class.
 *
 * This is necessary due to the "final" attribute of the BluetoothHeadset class. In order to
 * test the correct functioning of the BluetoothInCallService class, the final class must be put
 * into a container that can be mocked correctly.
 */
public class BluetoothHeadsetProxy {

    private BluetoothHeadset mBluetoothHeadset;

    public BluetoothHeadsetProxy(BluetoothHeadset headset) {
        mBluetoothHeadset = headset;
    }

    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
            String number, int type) {

        mBluetoothHeadset.clccResponse(index, direction, status, mode, mpty, number, type);
    }

    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
            int type, String name) {

        mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type,
                name);
    }

    public List<BluetoothDevice> getConnectedDevices() {
        return mBluetoothHeadset.getConnectedDevices();
    }

    public int getConnectionState(BluetoothDevice device) {
        return mBluetoothHeadset.getConnectionState(device);
    }

    public int getAudioState(BluetoothDevice device) {
        return mBluetoothHeadset.getAudioState(device);
    }

    public boolean connectAudio() {
        return mBluetoothHeadset.connectAudio();
    }

    public boolean setActiveDevice(BluetoothDevice device) {
        return mBluetoothHeadset.setActiveDevice(device);
    }

    public BluetoothDevice getActiveDevice() {
        return mBluetoothHeadset.getActiveDevice();
    }

    public boolean isAudioOn() {
        return mBluetoothHeadset.isAudioOn();
    }

    public boolean disconnectAudio() {
        return mBluetoothHeadset.disconnectAudio();
    }

    public boolean isInbandRingingEnabled() {
        return mBluetoothHeadset.isInbandRingingEnabled();
    }
}
Loading