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

Commit 3c03c8ba authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes Ic5f780de,I3f963307

* changes:
  Support audio-haptic coupled playback.
  Add buffer provider that can adjust channels.
parents 8dd9d107 245cdd91
Loading
Loading
Loading
Loading
+37 −4
Original line number Diff line number Diff line
@@ -78,6 +78,8 @@ public:
        DOWNMIX_TYPE    = 0X4004,
        MIXER_FORMAT    = 0x4005, // AUDIO_FORMAT_PCM_(FLOAT|16_BIT)
        MIXER_CHANNEL_MASK = 0x4006, // Channel mask for mixer output
        // for haptic
        HAPTIC_ENABLED  = 0x4007, // Set haptic data from this track should be played or not.
        // for target RESAMPLE
        SAMPLE_RATE     = 0x4100, // Configure sample rate conversion on this track name;
                                  // parameter 'value' is the new sample rate in Hz.
@@ -137,6 +139,13 @@ public:
    void        setBufferProvider(int name, AudioBufferProvider* bufferProvider);

    void        process() {
        for (const auto &pair : mTracks) {
            // Clear contracted buffer before processing if contracted channels are saved
            const std::shared_ptr<Track> &t = pair.second;
            if (t->mKeepContractedChannels) {
                t->clearContractedBuffer();
            }
        }
        (this->*mHook)();
    }

@@ -235,6 +244,8 @@ private:
            mPostDownmixReformatBufferProvider.reset(nullptr);
            mDownmixerBufferProvider.reset(nullptr);
            mReformatBufferProvider.reset(nullptr);
            mAdjustChannelsNonDestructiveBufferProvider.reset(nullptr);
            mAdjustChannelsBufferProvider.reset(nullptr);
        }

        bool        needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
@@ -249,6 +260,11 @@ private:
        void        unprepareForDownmix();
        status_t    prepareForReformat();
        void        unprepareForReformat();
        status_t    prepareForAdjustChannels();
        void        unprepareForAdjustChannels();
        status_t    prepareForAdjustChannelsNonDestructive(size_t frames);
        void        unprepareForAdjustChannelsNonDestructive();
        void        clearContractedBuffer();
        bool        setPlaybackRate(const AudioPlaybackRate &playbackRate);
        void        reconfigureBufferProviders();

@@ -302,17 +318,22 @@ private:
         * all pre-mixer track buffer conversions outside the AudioMixer class.
         *
         * 1) mInputBufferProvider: The AudioTrack buffer provider.
         * 2) mReformatBufferProvider: If not NULL, performs the audio reformat to
         * 2) mAdjustChannelsBufferProvider: Expend or contracts data
         * 3) mAdjustChannelsNonDestructiveBufferProvider: Non-destructively adjust sample data
         * 4) mReformatBufferProvider: If not NULL, performs the audio reformat to
         *    match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer
         *    requires reformat. For example, it may convert floating point input to
         *    PCM_16_bit if that's required by the downmixer.
         * 3) mDownmixerBufferProvider: If not NULL, performs the channel remixing to match
         * 5) mDownmixerBufferProvider: If not NULL, performs the channel remixing to match
         *    the number of channels required by the mixer sink.
         * 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
         * 6) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
         *    the downmixer requirements to the mixer engine input requirements.
         * 5) mTimestretchBufferProvider: Adds timestretching for playback rate
         * 7) mTimestretchBufferProvider: Adds timestretching for playback rate
         */
        AudioBufferProvider*     mInputBufferProvider;    // externally provided buffer provider.
        // TODO: combine AdjustChannelsBufferProvider and AdjustChannelsNonDestructiveBufferProvider
        std::unique_ptr<PassthruBufferProvider> mAdjustChannelsBufferProvider;
        std::unique_ptr<PassthruBufferProvider> mAdjustChannelsNonDestructiveBufferProvider;
        std::unique_ptr<PassthruBufferProvider> mReformatBufferProvider;
        std::unique_ptr<PassthruBufferProvider> mDownmixerBufferProvider;
        std::unique_ptr<PassthruBufferProvider> mPostDownmixReformatBufferProvider;
@@ -341,6 +362,18 @@ private:

        AudioPlaybackRate    mPlaybackRate;

        // Haptic
        bool                 mHapticPlaybackEnabled;
        audio_channel_mask_t mHapticChannelMask;
        uint32_t             mHapticChannelCount;
        audio_channel_mask_t mMixerHapticChannelMask;
        uint32_t             mMixerHapticChannelCount;
        uint32_t             mAdjustInChannelCount;
        uint32_t             mAdjustOutChannelCount;
        uint32_t             mAdjustNonDestructiveInChannelCount;
        uint32_t             mAdjustNonDestructiveOutChannelCount;
        bool                 mKeepContractedChannels;

    private:
        // hooks
        void track__genericResample(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
+141 −6
Original line number Diff line number Diff line
@@ -136,6 +136,9 @@ status_t AudioMixer::create(

        // no initialization needed
        // t->frameCount
        t->mHapticChannelMask = channelMask & AUDIO_CHANNEL_HAPTIC_ALL;
        t->mHapticChannelCount = audio_channel_count_from_out_mask(t->mHapticChannelMask);
        channelMask &= ~AUDIO_CHANNEL_HAPTIC_ALL;
        t->channelCount = audio_channel_count_from_out_mask(channelMask);
        t->enabled = false;
        ALOGV_IF(audio_channel_mask_get_bits(channelMask) != AUDIO_CHANNEL_OUT_STEREO,
@@ -162,6 +165,15 @@ status_t AudioMixer::create(
                AUDIO_CHANNEL_REPRESENTATION_POSITION, AUDIO_CHANNEL_OUT_STEREO);
        t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask);
        t->mPlaybackRate = AUDIO_PLAYBACK_RATE_DEFAULT;
        // haptic
        t->mHapticPlaybackEnabled = false;
        t->mMixerHapticChannelMask = AUDIO_CHANNEL_NONE;
        t->mMixerHapticChannelCount = 0;
        t->mAdjustInChannelCount = t->channelCount + t->mHapticChannelCount;
        t->mAdjustOutChannelCount = t->channelCount + t->mMixerHapticChannelCount;
        t->mAdjustNonDestructiveInChannelCount = t->mAdjustOutChannelCount;
        t->mAdjustNonDestructiveOutChannelCount = t->channelCount;
        t->mKeepContractedChannels = false;
        // Check the downmixing (or upmixing) requirements.
        status_t status = t->prepareForDownmix();
        if (status != OK) {
@@ -171,6 +183,8 @@ status_t AudioMixer::create(
        // prepareForDownmix() may change mDownmixRequiresFormat
        ALOGVV("mMixerFormat:%#x  mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
        t->prepareForReformat();
        t->prepareForAdjustChannelsNonDestructive(mFrameCount);
        t->prepareForAdjustChannels();

        mTracks[name] = t;
        return OK;
@@ -185,13 +199,20 @@ bool AudioMixer::setChannelMasks(int name,
    LOG_ALWAYS_FATAL_IF(!exists(name), "invalid name: %d", name);
    const std::shared_ptr<Track> &track = mTracks[name];

    if (trackChannelMask == track->channelMask
            && mixerChannelMask == track->mMixerChannelMask) {
    if (trackChannelMask == (track->channelMask | track->mHapticChannelMask)
            && mixerChannelMask == (track->mMixerChannelMask | track->mMixerHapticChannelMask)) {
        return false;  // no need to change
    }
    const audio_channel_mask_t hapticChannelMask = trackChannelMask & AUDIO_CHANNEL_HAPTIC_ALL;
    trackChannelMask &= ~AUDIO_CHANNEL_HAPTIC_ALL;
    const audio_channel_mask_t mixerHapticChannelMask = mixerChannelMask & AUDIO_CHANNEL_HAPTIC_ALL;
    mixerChannelMask &= ~AUDIO_CHANNEL_HAPTIC_ALL;
    // always recompute for both channel masks even if only one has changed.
    const uint32_t trackChannelCount = audio_channel_count_from_out_mask(trackChannelMask);
    const uint32_t mixerChannelCount = audio_channel_count_from_out_mask(mixerChannelMask);
    const uint32_t hapticChannelCount = audio_channel_count_from_out_mask(hapticChannelMask);
    const uint32_t mixerHapticChannelCount =
            audio_channel_count_from_out_mask(mixerHapticChannelMask);

    ALOG_ASSERT((trackChannelCount <= MAX_NUM_CHANNELS_TO_DOWNMIX)
            && trackChannelCount
@@ -200,6 +221,24 @@ bool AudioMixer::setChannelMasks(int name,
    track->channelCount = trackChannelCount;
    track->mMixerChannelMask = mixerChannelMask;
    track->mMixerChannelCount = mixerChannelCount;
    track->mHapticChannelMask = hapticChannelMask;
    track->mHapticChannelCount = hapticChannelCount;
    track->mMixerHapticChannelMask = mixerHapticChannelMask;
    track->mMixerHapticChannelCount = mixerHapticChannelCount;

    if (track->mHapticChannelCount > 0) {
        track->mAdjustInChannelCount = track->channelCount + track->mHapticChannelCount;
        track->mAdjustOutChannelCount = track->channelCount + track->mMixerHapticChannelCount;
        track->mAdjustNonDestructiveInChannelCount = track->mAdjustOutChannelCount;
        track->mAdjustNonDestructiveOutChannelCount = track->channelCount;
        track->mKeepContractedChannels = track->mHapticPlaybackEnabled;
    } else {
        track->mAdjustInChannelCount = 0;
        track->mAdjustOutChannelCount = 0;
        track->mAdjustNonDestructiveInChannelCount = 0;
        track->mAdjustNonDestructiveOutChannelCount = 0;
        track->mKeepContractedChannels = false;
    }

    // channel masks have changed, does this track need a downmixer?
    // update to try using our desired format (if we aren't already using it)
@@ -212,6 +251,9 @@ bool AudioMixer::setChannelMasks(int name,
    // do it after downmix since track format may change!
    track->prepareForReformat();

    track->prepareForAdjustChannelsNonDestructive(mFrameCount);
    track->prepareForAdjustChannels();

    if (track->mResampler.get() != nullptr) {
        // resampler channels may have changed.
        const uint32_t resetToSampleRate = track->sampleRate;
@@ -335,10 +377,82 @@ status_t AudioMixer::Track::prepareForReformat()
    return NO_ERROR;
}

void AudioMixer::Track::unprepareForAdjustChannels()
{
    ALOGV("AUDIOMIXER::unprepareForAdjustChannels");
    if (mAdjustChannelsBufferProvider.get() != nullptr) {
        mAdjustChannelsBufferProvider.reset(nullptr);
        reconfigureBufferProviders();
    }
}

status_t AudioMixer::Track::prepareForAdjustChannels()
{
    ALOGV("AudioMixer::prepareForAdjustChannels(%p) with inChannelCount: %u, outChannelCount: %u",
            this, mAdjustInChannelCount, mAdjustOutChannelCount);
    unprepareForAdjustChannels();
    if (mAdjustInChannelCount != mAdjustOutChannelCount) {
        mAdjustChannelsBufferProvider.reset(new AdjustChannelsBufferProvider(
                mFormat, mAdjustInChannelCount, mAdjustOutChannelCount, kCopyBufferFrameCount));
        reconfigureBufferProviders();
    }
    return NO_ERROR;
}

void AudioMixer::Track::unprepareForAdjustChannelsNonDestructive()
{
    ALOGV("AUDIOMIXER::unprepareForAdjustChannelsNonDestructive");
    if (mAdjustChannelsNonDestructiveBufferProvider.get() != nullptr) {
        mAdjustChannelsNonDestructiveBufferProvider.reset(nullptr);
        reconfigureBufferProviders();
    }
}

status_t AudioMixer::Track::prepareForAdjustChannelsNonDestructive(size_t frames)
{
    ALOGV("AudioMixer::prepareForAdjustChannelsNonDestructive(%p) with inChannelCount: %u, "
          "outChannelCount: %u, keepContractedChannels: %d",
            this, mAdjustNonDestructiveInChannelCount, mAdjustNonDestructiveOutChannelCount,
            mKeepContractedChannels);
    unprepareForAdjustChannelsNonDestructive();
    if (mAdjustNonDestructiveInChannelCount != mAdjustNonDestructiveOutChannelCount) {
        uint8_t* buffer = mKeepContractedChannels
                ? (uint8_t*)mainBuffer + frames * audio_bytes_per_frame(
                        mMixerChannelCount, mMixerFormat)
                : NULL;
        mAdjustChannelsNonDestructiveBufferProvider.reset(
                new AdjustChannelsNonDestructiveBufferProvider(
                        mFormat,
                        mAdjustNonDestructiveInChannelCount,
                        mAdjustNonDestructiveOutChannelCount,
                        mKeepContractedChannels ? mMixerFormat : AUDIO_FORMAT_INVALID,
                        frames,
                        buffer));
        reconfigureBufferProviders();
    }
    return NO_ERROR;
}

void AudioMixer::Track::clearContractedBuffer()
{
    if (mAdjustChannelsNonDestructiveBufferProvider.get() != nullptr) {
        static_cast<AdjustChannelsNonDestructiveBufferProvider*>(
                mAdjustChannelsNonDestructiveBufferProvider.get())->clearContractedFrames();
    }
}

void AudioMixer::Track::reconfigureBufferProviders()
{
    // configure from upstream to downstream buffer providers.
    bufferProvider = mInputBufferProvider;
    if (mAdjustChannelsBufferProvider.get() != nullptr) {
        mAdjustChannelsBufferProvider->setBufferProvider(bufferProvider);
        bufferProvider = mAdjustChannelsBufferProvider.get();
    }
    if (mAdjustChannelsNonDestructiveBufferProvider.get() != nullptr) {
        mAdjustChannelsNonDestructiveBufferProvider->setBufferProvider(bufferProvider);
        bufferProvider = mAdjustChannelsNonDestructiveBufferProvider.get();
    }
    if (mReformatBufferProvider.get() != nullptr) {
        mReformatBufferProvider->setBufferProvider(bufferProvider);
        bufferProvider = mReformatBufferProvider.get();
@@ -533,7 +647,8 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
        case CHANNEL_MASK: {
            const audio_channel_mask_t trackChannelMask =
                static_cast<audio_channel_mask_t>(valueInt);
            if (setChannelMasks(name, trackChannelMask, track->mMixerChannelMask)) {
            if (setChannelMasks(name, trackChannelMask,
                    (track->mMixerChannelMask | track->mMixerHapticChannelMask))) {
                ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", trackChannelMask);
                invalidate();
            }
@@ -542,6 +657,9 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
            if (track->mainBuffer != valueBuf) {
                track->mainBuffer = valueBuf;
                ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf);
                if (track->mKeepContractedChannels) {
                    track->prepareForAdjustChannelsNonDestructive(mFrameCount);
                }
                invalidate();
            }
            break;
@@ -571,16 +689,29 @@ void AudioMixer::setParameter(int name, int target, int param, void *value)
            if (track->mMixerFormat != format) {
                track->mMixerFormat = format;
                ALOGV("setParameter(TRACK, MIXER_FORMAT, %#x)", format);
                if (track->mKeepContractedChannels) {
                    track->prepareForAdjustChannelsNonDestructive(mFrameCount);
                }
            }
            } break;
        case MIXER_CHANNEL_MASK: {
            const audio_channel_mask_t mixerChannelMask =
                    static_cast<audio_channel_mask_t>(valueInt);
            if (setChannelMasks(name, track->channelMask, mixerChannelMask)) {
            if (setChannelMasks(name, track->channelMask | track->mHapticChannelMask,
                    mixerChannelMask)) {
                ALOGV("setParameter(TRACK, MIXER_CHANNEL_MASK, %#x)", mixerChannelMask);
                invalidate();
            }
            } break;
        case HAPTIC_ENABLED: {
            const bool hapticPlaybackEnabled = static_cast<bool>(valueInt);
            if (track->mHapticPlaybackEnabled != hapticPlaybackEnabled) {
                track->mHapticPlaybackEnabled = hapticPlaybackEnabled;
                track->mKeepContractedChannels = hapticPlaybackEnabled;
                track->prepareForAdjustChannelsNonDestructive(mFrameCount);
                track->prepareForAdjustChannels();
            }
            } break;
        default:
            LOG_ALWAYS_FATAL("setParameter track: bad param %d", param);
        }
@@ -823,6 +954,10 @@ void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider
        track->mDownmixerBufferProvider->reset();
    } else if (track->mReformatBufferProvider.get() != nullptr) {
        track->mReformatBufferProvider->reset();
    } else if (track->mAdjustChannelsNonDestructiveBufferProvider.get() != nullptr) {
        track->mAdjustChannelsNonDestructiveBufferProvider->reset();
    } else if (track->mAdjustChannelsBufferProvider.get() != nullptr) {
        track->mAdjustChannelsBufferProvider->reset();
    }

    track->mInputBufferProvider = bufferProvider;
@@ -1266,8 +1401,8 @@ void AudioMixer::process__nop()

        const std::shared_ptr<Track> &t = mTracks[group[0]];
        memset(t->mainBuffer, 0,
                mFrameCount * t->mMixerChannelCount
                * audio_bytes_per_sample(t->mMixerFormat));
                mFrameCount * audio_bytes_per_frame(
                        t->mMixerChannelCount + t->mMixerHapticChannelCount, t->mMixerFormat));

        // now consume data
        for (const int name : group) {
+79 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@

#include <audio_utils/primitives.h>
#include <audio_utils/format.h>
#include <audio_utils/channels.h>
#include <external/sonic/sonic.h>
#include <media/audiohal/EffectBufferHalInterface.h>
#include <media/audiohal/EffectHalInterface.h>
@@ -630,5 +631,83 @@ void TimestretchBufferProvider::processFrames(void *dstBuffer, size_t *dstFrames
        }
    }
}

AdjustChannelsBufferProvider::AdjustChannelsBufferProvider(audio_format_t format,
        size_t inChannelCount, size_t outChannelCount, size_t frameCount) :
        CopyBufferProvider(
                audio_bytes_per_frame(inChannelCount, format),
                audio_bytes_per_frame(outChannelCount, format),
                frameCount),
        mFormat(format),
        mInChannelCount(inChannelCount),
        mOutChannelCount(outChannelCount),
        mSampleSizeInBytes(audio_bytes_per_sample(format))
{
    ALOGV("AdjustBufferProvider(%p)(%#x, %zu, %zu, %zu)",
            this, format, inChannelCount, outChannelCount, frameCount);
}

void AdjustChannelsBufferProvider::copyFrames(void *dst, const void *src, size_t frames)
{
    adjust_channels(src, mInChannelCount, dst, mOutChannelCount, mSampleSizeInBytes,
            frames * mInChannelCount * mSampleSizeInBytes);
}

AdjustChannelsNonDestructiveBufferProvider::AdjustChannelsNonDestructiveBufferProvider(
        audio_format_t format, size_t inChannelCount, size_t outChannelCount,
        audio_format_t contractedFormat, size_t contractedFrameCount, void* contractedBuffer) :
        CopyBufferProvider(
                audio_bytes_per_frame(inChannelCount, format),
                audio_bytes_per_frame(outChannelCount, format),
                0 /*bufferFrameCount*/),
        mFormat(format),
        mInChannelCount(inChannelCount),
        mOutChannelCount(outChannelCount),
        mSampleSizeInBytes(audio_bytes_per_sample(format)),
        mContractedChannelCount(inChannelCount - outChannelCount),
        mContractedFormat(contractedFormat),
        mContractedFrameCount(contractedFrameCount),
        mContractedBuffer(contractedBuffer),
        mContractedWrittenFrames(0)
{
    ALOGV("AdjustChannelsNonDestructiveBufferProvider(%p)(%#x, %zu, %zu, %#x, %p)",
            this, format, inChannelCount, outChannelCount, contractedFormat, contractedBuffer);
    if (mContractedFormat != AUDIO_FORMAT_INVALID && mInChannelCount > mOutChannelCount) {
        mContractedFrameSize = audio_bytes_per_frame(mContractedChannelCount, mContractedFormat);
    }
}

status_t AdjustChannelsNonDestructiveBufferProvider::getNextBuffer(
        AudioBufferProvider::Buffer* pBuffer)
{
    const size_t outFramesLeft = mContractedFrameCount - mContractedWrittenFrames;
    if (outFramesLeft < pBuffer->frameCount) {
        // Restrict the frame count so that we don't write over the size of the output buffer.
        pBuffer->frameCount = outFramesLeft;
    }
    return CopyBufferProvider::getNextBuffer(pBuffer);
}

void AdjustChannelsNonDestructiveBufferProvider::copyFrames(
        void *dst, const void *src, size_t frames)
{
    adjust_channels_non_destructive(src, mInChannelCount, dst, mOutChannelCount, mSampleSizeInBytes,
            frames * mInChannelCount * mSampleSizeInBytes);
    if (mContractedFormat != AUDIO_FORMAT_INVALID && mContractedBuffer != NULL
            && mInChannelCount > mOutChannelCount) {
        const size_t contractedIdx = frames * mOutChannelCount * mSampleSizeInBytes;
        memcpy_by_audio_format(
                (uint8_t*)mContractedBuffer + mContractedWrittenFrames * mContractedFrameSize,
                mContractedFormat, (uint8_t*)dst + contractedIdx, mFormat,
                mContractedChannelCount * frames);
        mContractedWrittenFrames += frames;
    }
}

void AdjustChannelsNonDestructiveBufferProvider::reset()
{
    mContractedWrittenFrames = 0;
    CopyBufferProvider::reset();
}
// ----------------------------------------------------------------------------
} // namespace android
+47 −0
Original line number Diff line number Diff line
@@ -218,6 +218,53 @@ private:
    bool                 mAudioPlaybackRateValid; // flag for current parameters validity
};

// AdjustBufferProvider derives from CopyBufferProvider to adjust sample data.
// Expands or contracts sample data from one interleaved channel format to another.
// Expanded channels are filled with zeros and put at the end of each audio frame.
// Contracted channels are omitted from the end of each audio frame.
class AdjustChannelsBufferProvider : public CopyBufferProvider {
public:
    AdjustChannelsBufferProvider(audio_format_t format, size_t inChannelCount,
            size_t outChannelCount, size_t frameCount);
    //Overrides
    void copyFrames(void *dst, const void *src, size_t frames) override;

protected:
    const audio_format_t mFormat;
    const size_t         mInChannelCount;
    const size_t         mOutChannelCount;
    const size_t         mSampleSizeInBytes;
};

// AdjustChannelsNonDestructiveBufferProvider derives from CopyBufferProvider to adjust sample data.
// Expands or contracts sample data from one interleaved channel format to another.
// Extra expanded channels are interleaved in from the end of the input buffer.
// Contracted channels are copied to the end of the output buffer.
// Contracted channels could be written to output buffer.
class AdjustChannelsNonDestructiveBufferProvider : public CopyBufferProvider {
public:
    AdjustChannelsNonDestructiveBufferProvider(audio_format_t format, size_t inChannelCount,
            size_t outChannelCount, audio_format_t contractedFormat, size_t contractedFrameCount,
            void* contractedBuffer);
    //Overrides
    status_t getNextBuffer(Buffer* pBuffer) override;
    void copyFrames(void *dst, const void *src, size_t frames) override;
    void reset() override;

    void clearContractedFrames() { mContractedWrittenFrames = 0; }

protected:
    const audio_format_t mFormat;
    const size_t         mInChannelCount;
    const size_t         mOutChannelCount;
    const size_t         mSampleSizeInBytes;
    const size_t         mContractedChannelCount;
    const audio_format_t mContractedFormat;
    const size_t         mContractedFrameCount;
    void                *mContractedBuffer;
    size_t               mContractedWrittenFrames;
    size_t               mContractedFrameSize;
};
// ----------------------------------------------------------------------------
} // namespace android

+20 −4

File changed.

Preview size limit exceeded, changes collapsed.

Loading