blob: 0f013ff73eda4c8f8205499e675ba0c39ce799e0 [file] [log] [blame]
/*
* Copyright (C) 2021 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.systemui.communal.service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.os.IBinder;
import android.os.PatternMatcher;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.communal.CommunalSourceMonitor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.communal.ICommunalSource;
import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
/**
* The {@link CommunalSourcePrimer} is responsible for priming SystemUI with a pre-configured
* Communal source. The SystemUI service binds to the component to retrieve the
* {@link com.android.systemui.communal.CommunalSource}. {@link CommunalSourcePrimer} has no effect
* if there is no pre-defined value.
*/
@SysUISingleton
public class CommunalSourcePrimer extends SystemUI {
private static final String TAG = "CommunalSourcePrimer";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String ACTION_COMMUNAL_SOURCE = "android.intent.action.COMMUNAL_SOURCE";
private final Context mContext;
private final DelayableExecutor mMainExecutor;
private final CommunalSourceMonitor mMonitor;
private final CommunalSourceImpl.Factory mSourceFactory;
private final ComponentName mComponentName;
private final int mBaseReconnectDelayMs;
private final int mMaxReconnectAttempts;
private int mReconnectAttempts = 0;
private Runnable mCurrentReconnectCancelable;
private final Runnable mConnectRunnable = new Runnable() {
@Override
public void run() {
mCurrentReconnectCancelable = null;
bindToService();
}
};
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
Log.d(TAG, "package added receiver - onReceive");
}
initiateConnectionAttempt();
}
};
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
final ICommunalSource source = ICommunalSource.Stub.asInterface(service);
if (DEBUG) {
Log.d(TAG, "onServiceConnected. source:" + source);
}
if (source == null) {
if (DEBUG) {
Log.d(TAG, "onServiceConnected. invalid source");
// Since the service could just repeatedly return null, the primer chooses
// to schedule rather than initiate a new connection attempt sequence.
scheduleConnectionAttempt();
}
return;
}
mMonitor.setSource(mSourceFactory.create(source));
}
@Override
public void onBindingDied(ComponentName name) {
if (DEBUG) {
Log.d(TAG, "onBindingDied. lost communal source. initiating reconnect");
}
initiateConnectionAttempt();
}
@Override
public void onServiceDisconnected(ComponentName className) {
if (DEBUG) {
Log.d(TAG,
"onServiceDisconnected. lost communal source. initiating reconnect");
}
initiateConnectionAttempt();
}
};
@Inject
public CommunalSourcePrimer(Context context, @Main Resources resources,
DelayableExecutor mainExecutor,
CommunalSourceMonitor monitor,
CommunalSourceImpl.Factory sourceFactory) {
super(context);
mContext = context;
mMainExecutor = mainExecutor;
mMonitor = monitor;
mSourceFactory = sourceFactory;
mMaxReconnectAttempts = resources.getInteger(
R.integer.config_communalSourceMaxReconnectAttempts);
mBaseReconnectDelayMs = resources.getInteger(
R.integer.config_communalSourceReconnectBaseDelay);
final String component = resources.getString(R.string.config_communalSourceComponent);
mComponentName = component != null && !component.isEmpty()
? ComponentName.unflattenFromString(component) : null;
}
@Override
public void start() {
}
private void initiateConnectionAttempt() {
// Reset attempts
mReconnectAttempts = 0;
mMonitor.setSource(null);
// The first attempt is always a direct invocation rather than delayed.
bindToService();
}
private void registerPackageListening() {
if (mComponentName == null) {
return;
}
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
filter.addDataSchemeSpecificPart(mComponentName.getPackageName(),
PatternMatcher.PATTERN_LITERAL);
// Note that we directly register the receiver here as data schemes are not supported by
// BroadcastDispatcher.
mContext.registerReceiver(mReceiver, filter);
}
private void scheduleConnectionAttempt() {
// always clear cancelable if present.
if (mCurrentReconnectCancelable != null) {
mCurrentReconnectCancelable.run();
mCurrentReconnectCancelable = null;
}
if (mReconnectAttempts >= mMaxReconnectAttempts) {
if (DEBUG) {
Log.d(TAG, "exceeded max connection attempts.");
}
return;
}
final long reconnectDelayMs =
(long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
if (DEBUG) {
Log.d(TAG,
"scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
}
mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
mReconnectAttempts++;
}
@Override
protected void onBootCompleted() {
super.onBootCompleted();
if (DEBUG) {
Log.d(TAG, "onBootCompleted. communal source component:" + mComponentName);
}
registerPackageListening();
initiateConnectionAttempt();
}
private void bindToService() {
if (mComponentName == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "attempting to bind to communal source");
}
final Intent intent = new Intent();
intent.setAction(ACTION_COMMUNAL_SOURCE);
intent.setComponent(mComponentName);
final boolean binding = mContext.bindService(intent, Context.BIND_AUTO_CREATE,
mMainExecutor, mConnection);
if (!binding) {
if (DEBUG) {
Log.d(TAG, "bindService failed, rescheduling");
}
scheduleConnectionAttempt();
}
}
}