Merge "[adb-wireless] Add Wireless Debugging Switch in Developer options."

This commit is contained in:
Joshua Duong
2020-02-21 16:03:29 +00:00
committed by Android (Google) Code Review
24 changed files with 2869 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
/*
* 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.settings.development;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.debug.PairDevice;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.ActionButtonsPreference;
/**
* Controller for logic pertaining to displaying adb device information for the
* {@link AdbDeviceDetailsFragment}.
*/
public class AdbDeviceDetailsActionController extends AbstractPreferenceController {
private static final String TAG = "AdbDeviceDetailsAction";
@VisibleForTesting
static final String KEY_BUTTONS_PREF = "buttons";
private PairDevice mPairedDevice;
private final Fragment mFragment;
private ActionButtonsPreference mButtonsPref;
public AdbDeviceDetailsActionController(
PairDevice pairedDevice,
Context context,
Fragment fragment) {
super(context);
mPairedDevice = pairedDevice;
mFragment = fragment;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_BUTTONS_PREF;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mButtonsPref = ((ActionButtonsPreference) screen.findPreference(getPreferenceKey()))
.setButton1Visible(false)
.setButton2Icon(R.drawable.ic_settings_delete)
.setButton2Text(R.string.adb_device_forget)
.setButton2OnClickListener(view -> forgetDevice());
}
/**
* Forgets the device.
*/
private void forgetDevice() {
Intent intent = new Intent();
intent.putExtra(
WirelessDebuggingFragment.PAIRED_DEVICE_REQUEST_TYPE,
WirelessDebuggingFragment.FORGET_ACTION);
intent.putExtra(
WirelessDebuggingFragment.PAIRED_DEVICE_EXTRA,
mPairedDevice);
mFragment.getActivity().setResult(Activity.RESULT_OK, intent);
mFragment.getActivity().finish();
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.settings.development;
import android.content.Context;
import android.debug.PairDevice;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.FooterPreference;
/**
* Controller for logic pertaining to displaying adb device information for the
* {@link AdbDeviceDetailsFragment}.
*/
public class AdbDeviceDetailsFingerprintController extends AbstractPreferenceController {
private static final String TAG = "AdbDeviceDetailsFinger";
@VisibleForTesting
static final String KEY_FINGERPRINT_CATEGORY = "fingerprint_category";
private PairDevice mPairedDevice;
private final Fragment mFragment;
private PreferenceCategory mFingerprintCategory;
private FooterPreference mFingerprintPref;
public AdbDeviceDetailsFingerprintController(
PairDevice pairedDevice,
Context context,
Fragment fragment) {
super(context);
mPairedDevice = pairedDevice;
mFragment = fragment;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_FINGERPRINT_CATEGORY;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mFingerprintCategory = (PreferenceCategory) screen.findPreference(getPreferenceKey());
mFingerprintPref = new FooterPreference(mFingerprintCategory.getContext());
final CharSequence titleFormat = mContext.getText(
R.string.adb_device_fingerprint_title_format);
mFingerprintPref.setTitle(String.format(
titleFormat.toString(), mPairedDevice.getGuid()));
mFingerprintCategory.addPreference(mFingerprintPref);
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.settings.development;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.debug.PairDevice;
import android.os.Bundle;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Fragment shown when clicking on a paired device in the Wireless
* Debugging fragment.
*/
public class AdbDeviceDetailsFragment extends DashboardFragment {
private static final String TAG = "AdbDeviceDetailsFrag";
private PairDevice mPairedDevice;
public AdbDeviceDetailsFragment() {
super();
}
@Override
public void onAttach(Context context) {
// Get the paired device stored in the extras
Bundle bundle = getArguments();
if (bundle.containsKey(AdbPairedDevicePreference.PAIRED_DEVICE_EXTRA)) {
mPairedDevice = (PairDevice) bundle.getParcelable(
AdbPairedDevicePreference.PAIRED_DEVICE_EXTRA);
}
super.onAttach(context);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.ADB_WIRELESS_DEVICE_DETAILS;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.adb_device_details_fragment;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new AdbDeviceDetailsHeaderController(mPairedDevice, context, this));
controllers.add(new AdbDeviceDetailsActionController(mPairedDevice, context, this));
controllers.add(new AdbDeviceDetailsFingerprintController(mPairedDevice, context, this));
return controllers;
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.settings.development;
import android.content.Context;
import android.debug.PairDevice;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.widget.LayoutPreference;
/**
* Controller for logic pertaining to displaying adb device information for the
* {@link AdbDeviceDetailsFragment}.
*/
public class AdbDeviceDetailsHeaderController extends AbstractPreferenceController
implements PreferenceControllerMixin, LifecycleObserver {
private static final String TAG = "AdbDeviceDetailsHeader";
@VisibleForTesting
static final String KEY_HEADER = "adb_device_header";
private PairDevice mPairedDevice;
private final Fragment mFragment;
private EntityHeaderController mEntityHeaderController;
public AdbDeviceDetailsHeaderController(
PairDevice pairedDevice,
Context context,
Fragment fragment) {
super(context);
mPairedDevice = pairedDevice;
mFragment = fragment;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_HEADER;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
setupEntityHeader(screen);
}
private void setupEntityHeader(PreferenceScreen screen) {
LayoutPreference headerPref = (LayoutPreference) screen.findPreference(KEY_HEADER);
mEntityHeaderController =
EntityHeaderController.newInstance(
mFragment.getActivity(), mFragment,
headerPref.findViewById(R.id.entity_header));
mEntityHeaderController
.setIcon(mContext.getDrawable(com.android.internal.R.drawable.ic_bt_laptop))
.setLabel(mPairedDevice.getDeviceName())
.done(mFragment.getActivity(), true);
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.settings.development;
import static android.content.Context.CLIPBOARD_SERVICE;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.widget.Toast;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/**
* Controller for the device name preference in the Wireless debugging
* fragment.
*/
public class AdbDeviceNamePreferenceController extends BasePreferenceController {
private static final String TAG = "AdbDeviceNamePrefCtrl";
private String mDeviceName;
public AdbDeviceNamePreferenceController(Context context, String key) {
super(context, key);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
// Keep device name in sync with Settings > About phone > Device name
mDeviceName = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.DEVICE_NAME);
if (mDeviceName == null) {
mDeviceName = Build.MODEL;
}
}
@Override
public CharSequence getSummary() {
return mDeviceName;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void copy() {
final ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(
CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText("text", mDeviceName));
final String toast = mContext.getString(R.string.copyable_slice_toast,
mDeviceName);
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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.settings.development;
import android.content.Context;
import android.debug.IAdbManager;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.wifi.WifiManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.deviceinfo.AbstractConnectivityPreferenceController;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.Iterator;
/**
* Controller for the ip address preference in the Wireless debugging
* fragment.
*/
public class AdbIpAddressPreferenceController extends AbstractConnectivityPreferenceController {
private static final String TAG = "AdbIpAddrPrefCtrl";
private static final String[] CONNECTIVITY_INTENTS = {
ConnectivityManager.CONNECTIVITY_ACTION,
WifiManager.ACTION_LINK_CONFIGURATION_CHANGED,
WifiManager.NETWORK_STATE_CHANGED_ACTION,
};
private static final String PREF_KEY = "adb_ip_addr_pref";
private Preference mAdbIpAddrPref;
private int mPort;
private final ConnectivityManager mCM;
private IAdbManager mAdbManager;
public AdbIpAddressPreferenceController(Context context, Lifecycle lifecycle) {
super(context, lifecycle);
mCM = context.getSystemService(ConnectivityManager.class);
mAdbManager = IAdbManager.Stub.asInterface(ServiceManager.getService(
Context.ADB_SERVICE));
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return PREF_KEY;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mAdbIpAddrPref = screen.findPreference(PREF_KEY);
updateConnectivity();
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
updateConnectivity();
}
@Override
protected String[] getConnectivityIntents() {
return CONNECTIVITY_INTENTS;
}
protected int getPort() {
try {
return mAdbManager.getAdbWirelessPort();
} catch (RemoteException e) {
Log.e(TAG, "Unable to get the adbwifi port");
}
return 0;
}
public String getIpv4Address() {
return getDefaultIpAddresses(mCM);
}
@Override
protected void updateConnectivity() {
String ipAddress = getDefaultIpAddresses(mCM);
if (ipAddress != null) {
int port = getPort();
if (port <= 0) {
mAdbIpAddrPref.setSummary(R.string.status_unavailable);
} else {
ipAddress += ":" + port;
}
mAdbIpAddrPref.setSummary(ipAddress);
} else {
mAdbIpAddrPref.setSummary(R.string.status_unavailable);
}
}
/**
* Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
* addresses.
* @param cm ConnectivityManager
* @return the formatted and newline-separated IP addresses, or null if none.
*/
private static String getDefaultIpAddresses(ConnectivityManager cm) {
LinkProperties prop = cm.getActiveLinkProperties();
return formatIpAddresses(prop);
}
private static String formatIpAddresses(LinkProperties prop) {
if (prop == null) {
return null;
}
Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
// If there are no entries, return null
if (!iter.hasNext()) {
return null;
}
// Concatenate all available addresses, newline separated
StringBuilder addresses = new StringBuilder();
while (iter.hasNext()) {
InetAddress addr = iter.next();
if (addr instanceof Inet4Address) {
// adb only supports ipv4 at the moment
addresses.append(addr.getHostAddress());
break;
}
}
return addresses.toString();
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.settings.development;
import android.content.Context;
import android.debug.PairDevice;
import android.os.Bundle;
import android.view.View;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/**
* An AP preference for the currently connected AP
*/
public class AdbPairedDevicePreference extends Preference {
private static final String TAG = "AdbPairedDevicePref";
private PairDevice mPairedDevice;
// Extract using getSerializable(PAIRED_DEVICE_EXTRA)
public static final String PAIRED_DEVICE_EXTRA = "paired_device";
public AdbPairedDevicePreference(PairDevice pairedDevice, Context context) {
super(context);
mPairedDevice = pairedDevice;
setWidgetLayoutResource(getWidgetLayoutResourceId());
refresh();
}
protected int getWidgetLayoutResourceId() {
return R.layout.preference_widget_gear_optional_background;
}
/**
* Refreshes the preference bound to the paired device previously passed in.
*/
public void refresh() {
setTitle(this, mPairedDevice);
}
public void setPairedDevice(PairDevice pairedDevice) {
mPairedDevice = pairedDevice;
}
public PairDevice getPairedDevice() {
return mPairedDevice;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final View gear = holder.findViewById(R.id.settings_button);
final View gearNoBg = holder.findViewById(R.id.settings_button_no_background);
gear.setVisibility(View.INVISIBLE);
gearNoBg.setVisibility(View.VISIBLE);
}
static void setTitle(AdbPairedDevicePreference preference,
PairDevice pairedDevice) {
preference.setTitle(pairedDevice.getDeviceName());
preference.setSummary(pairedDevice.isConnected()
? preference.getContext().getText(R.string.adb_wireless_device_connected_summary)
: "");
}
/**
* Writes the paired devices bound to this preference to the bundle.
*
* @param bundle the bundle to write the paired device to
*/
public void savePairedDeviceToExtras(Bundle bundle) {
bundle.putParcelable(PAIRED_DEVICE_EXTRA, mPairedDevice);
}
}

View File

@@ -0,0 +1,326 @@
/*
* 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.settings.development;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.debug.AdbManager;
import android.debug.IAdbManager;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import androidx.annotation.StringRes;
import com.android.settings.R;
import com.android.settings.wifi.dpp.AdbQrCode;
import com.android.settings.wifi.dpp.WifiDppQrCodeBaseFragment;
import com.android.settings.wifi.dpp.WifiNetworkConfig;
import com.android.settings.wifi.qrcode.QrCamera;
import com.android.settings.wifi.qrcode.QrDecorateView;
/**
* Fragment shown when clicking on the "Pair by QR code" preference in
* the Wireless Debugging fragment.
*/
public class AdbQrcodeScannerFragment extends WifiDppQrCodeBaseFragment implements
SurfaceTextureListener,
QrCamera.ScannerCallback {
private static final String TAG = "AdbQrcodeScannerFrag";
/** Message sent to hide error message */
private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
/** Message sent to show error message */
private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
private QrCamera mCamera;
private TextureView mTextureView;
private QrDecorateView mDecorateView;
private View mQrCameraView;
private View mVerifyingView;
private TextView mErrorMessage;
/** QR code data scanned by camera */
private AdbQrCode mAdbQrCode;
private WifiNetworkConfig mAdbConfig;
private IAdbManager mAdbManager;
private IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) {
Integer res = intent.getIntExtra(
AdbManager.WIRELESS_STATUS_EXTRA,
AdbManager.WIRELESS_STATUS_FAIL);
if (res.equals(AdbManager.WIRELESS_STATUS_SUCCESS)) {
Intent i = new Intent();
i.putExtra(
WirelessDebuggingFragment.PAIRING_DEVICE_REQUEST_TYPE,
WirelessDebuggingFragment.SUCCESS_ACTION);
getActivity().setResult(Activity.RESULT_OK, i);
getActivity().finish();
} else if (res.equals(AdbManager.WIRELESS_STATUS_FAIL)) {
Intent i = new Intent();
i.putExtra(
WirelessDebuggingFragment.PAIRING_DEVICE_REQUEST_TYPE,
WirelessDebuggingFragment.FAIL_ACTION);
getActivity().setResult(Activity.RESULT_OK, i);
getActivity().finish();
} else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) {
int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0);
Log.i(TAG, "Got Qr pairing code port=" + port);
}
}
}
};
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_HIDE_ERROR_MESSAGE:
mErrorMessage.setVisibility(View.INVISIBLE);
break;
case MESSAGE_SHOW_ERROR_MESSAGE:
final String errorMessage = (String) msg.obj;
mErrorMessage.setVisibility(View.VISIBLE);
mErrorMessage.setText(errorMessage);
mErrorMessage.sendAccessibilityEvent(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
// Cancel any pending messages to hide error view and requeue the message so
// user has time to see error
removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE,
SHOW_ERROR_MESSAGE_INTERVAL);
break;
default:
return;
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIntentFilter = new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.adb_qrcode_scanner_fragment, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTextureView = (TextureView) view.findViewById(R.id.preview_view);
mTextureView.setSurfaceTextureListener(this);
mDecorateView = view.findViewById(R.id.decorate_view);
setProgressBarShown(false);
setHeaderIconImageResource(R.drawable.ic_scan_24dp);
mQrCameraView = view.findViewById(R.id.camera_layout);
mVerifyingView = view.findViewById(R.id.verifying_layout);
setHeaderTitle(R.string.wifi_dpp_scan_qr_code);
mSummary.setText(R.string.adb_wireless_qrcode_pairing_description);
mErrorMessage = view.findViewById(R.id.error_message);
}
@Override
public void onResume() {
super.onResume();
mAdbManager = IAdbManager.Stub.asInterface(ServiceManager.getService(Context.ADB_SERVICE));
getActivity().registerReceiver(mReceiver, mIntentFilter);
}
@Override
public void onPause() {
super.onPause();
getActivity().unregisterReceiver(mReceiver);
try {
mAdbManager.disablePairing();
} catch (RemoteException e) {
Log.e(TAG, "Unable to cancel pairing");
}
getActivity().finish();
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// Do nothing
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public int getMetricsCategory() {
return 0;
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
initCamera(surface);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
// Do nothing
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
destroyCamera();
return true;
}
@Override
public Size getViewSize() {
return new Size(mTextureView.getWidth(), mTextureView.getHeight());
}
@Override
public void setTransform(Matrix transform) {
mTextureView.setTransform(transform);
}
@Override
public Rect getFramePosition(Size previewSize, int cameraOrientation) {
return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
}
@Override
public boolean isValid(String qrCode) {
try {
// WIFI:T:ADB;S:myname;P:mypass;;
mAdbQrCode = new AdbQrCode(qrCode);
} catch (IllegalArgumentException e) {
showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format);
return false;
}
mAdbConfig = mAdbQrCode.getAdbNetworkConfig();
return true;
}
@Override
public void handleSuccessfulResult(String qrCode) {
destroyCamera();
mDecorateView.setFocused(true);
mQrCameraView.setVisibility(View.GONE);
mVerifyingView.setVisibility(View.VISIBLE);
try {
mAdbManager.enablePairingByQrCode(mAdbConfig.getSsid(),
mAdbConfig.getPreSharedKey());
} catch (RemoteException e) {
Log.e(TAG, "Unable to enable QR code pairing");
getActivity().finish();
}
}
@Override
public void handleCameraFailure() {
destroyCamera();
}
private void initCamera(SurfaceTexture surface) {
// Check if the camera has alread been created.
if (mCamera == null) {
mCamera = new QrCamera(getContext(), this);
mCamera.start(surface);
}
}
/**
* To resume camera decoding task after handshake fail or Wi-Fi connection fail.
*/
private void restartCamera() {
if (mCamera == null) {
Log.d(TAG, "mCamera is not available for restarting camera");
return;
}
if (mCamera.isDecodeTaskAlive()) {
mCamera.stop();
}
final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
if (surfaceTexture == null) {
throw new IllegalStateException("SurfaceTexture is not ready for restarting camera");
}
mCamera.start(surfaceTexture);
}
private void destroyCamera() {
if (mCamera != null) {
mCamera.stop();
mCamera = null;
}
}
private void showErrorMessage(@StringRes int messageResId) {
final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
getString(messageResId));
message.sendToTarget();
}
@Override
protected boolean isFooterAvailable() {
return false;
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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.settings.development;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
/**
* Class to show a variety of dialogs for the Wireless debugging
* fragment.
*/
public class AdbWirelessDialog extends AlertDialog implements
AdbWirelessDialogUiBase,
DialogInterface.OnClickListener {
/**
* Interface for subscribers to implement in order to listen
* to AdbWirelessDialog events.
*/
public interface AdbWirelessDialogListener {
/**
* Called when the dialog was closed by clicking a negative button.
*/
default void onCancel() {
}
/**
* Called when the dialog was closed by clicking a positive button.
*
* @param dialog the dialog that was closed.
*/
default void onSubmit(AdbWirelessDialog dialog) {
}
/**
* Called when the dialog was dismissed.
*/
default void onDismiss() {
}
}
private static final String TAG = "AdbWirelessDialog";
private static final int BUTTON_CANCEL = DialogInterface.BUTTON_NEGATIVE;
private static final int BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE;
private final AdbWirelessDialogListener mListener;
private final int mMode;
private View mView;
private AdbWirelessDialogController mController;
/**
* Creates a AdbWirelessDialog with no additional style. It displays as a dialog above the
* current view.
*/
public static AdbWirelessDialog createModal(
Context context,
AdbWirelessDialogListener listener,
int mode) {
return new AdbWirelessDialog(context, listener, mode);
}
AdbWirelessDialog(Context context, AdbWirelessDialogListener listener, int mode) {
super(context);
mListener = listener;
mMode = mode;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
mView = getLayoutInflater().inflate(R.layout.adb_wireless_dialog, null);
setView(mView);
mController = new AdbWirelessDialogController(this, mView, mMode);
super.onCreate(savedInstanceState);
}
@Override
protected void onStop() {
super.onStop();
dismiss();
if (mListener != null) {
mListener.onDismiss();
}
}
@Override
public void onClick(DialogInterface dialogInterface, int id) {
if (mListener != null) {
switch (id) {
case BUTTON_CANCEL:
mListener.onCancel();
break;
}
}
}
@Override
public AdbWirelessDialogController getController() {
return mController;
}
@Override
public void dispatchSubmit() {
if (mListener != null) {
mListener.onSubmit(this);
}
dismiss();
}
@Override
public int getMode() {
return mMode;
}
@Override
public Button getSubmitButton() {
return getButton(BUTTON_SUBMIT);
}
@Override
public Button getCancelButton() {
return getButton(BUTTON_NEGATIVE);
}
@Override
public void setSubmitButton(CharSequence text) {
setButton(BUTTON_SUBMIT, text, this);
}
@Override
public void setCancelButton(CharSequence text) {
setButton(BUTTON_NEGATIVE, text, this);
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.settings.development;
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
import android.widget.TextView;
import com.android.settings.R;
/**
* The class for allowing UIs like {@link AdbWirelessDialog} and {@link AdbWirelessDialogUiBase} to
* share the logic for controlling buttons, text fields, etc.
*/
public class AdbWirelessDialogController {
private static final String TAG = "AdbWirelessDialogCtrl";
private final AdbWirelessDialogUiBase mUi;
private final View mView;
private int mMode;
// The dialog for showing the six-digit code
private TextView mPairingCodeTitle;
private TextView mSixDigitCode;
private TextView mIpAddr;
// The dialog for showing pairing failed message
private TextView mFailedMsg;
private Context mContext;
public AdbWirelessDialogController(AdbWirelessDialogUiBase parent, View view,
int mode) {
mUi = parent;
mView = view;
mMode = mode;
mContext = mUi.getContext();
final Resources res = mContext.getResources();
mSixDigitCode = mView.findViewById(R.id.pairing_code);
mIpAddr = mView.findViewById(R.id.ip_addr);
switch (mMode) {
case AdbWirelessDialogUiBase.MODE_PAIRING:
String title = res.getString(R.string.adb_pairing_device_dialog_title);
mUi.setTitle(title);
mView.findViewById(R.id.l_pairing_six_digit).setVisibility(View.VISIBLE);
mUi.setCancelButton(res.getString(R.string.cancel));
mUi.setCanceledOnTouchOutside(false);
break;
case AdbWirelessDialogUiBase.MODE_PAIRING_FAILED:
String msg = res.getString(R.string.adb_pairing_device_dialog_failed_msg);
mUi.setTitle(R.string.adb_pairing_device_dialog_failed_title);
mView.findViewById(R.id.l_pairing_failed).setVisibility(View.VISIBLE);
mFailedMsg = (TextView) mView.findViewById(R.id.pairing_failed_label);
mFailedMsg.setText(msg);
mUi.setSubmitButton(res.getString(R.string.okay));
break;
case AdbWirelessDialogUiBase.MODE_QRCODE_FAILED:
mUi.setTitle(R.string.adb_pairing_device_dialog_failed_title);
mView.findViewById(R.id.l_qrcode_pairing_failed).setVisibility(View.VISIBLE);
mUi.setSubmitButton(res.getString(R.string.okay));
break;
}
// After done view show and hide, request focus from parent view
mView.findViewById(R.id.l_adbwirelessdialog).requestFocus();
}
/**
* Set the pairing code UI text field to code.
*
* @param code the pairing code string
*/
public void setPairingCode(String code) {
mSixDigitCode.setText(code);
}
/**
* Set the Ip address UI text field to ipAddr.
*
* @param ipAddr the ip address string
*/
public void setIpAddr(String ipAddr) {
mIpAddr.setText(ipAddr);
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.settings.development;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.Button;
/**
* Foundation interface glues between Activities and UIs like {@link AdbWirelessDialog}.
*/
public interface AdbWirelessDialogUiBase {
/**
* Dialog shown when pairing a device via six-digit code.
*/
int MODE_PAIRING = 0;
/**
* Dialog shown when connecting to a paired device failed.
*/
int MODE_CONNECTION_FAILED = 1;
/**
* Dialog shown when pairing failed.
*/
int MODE_PAIRING_FAILED = 2;
/**
* Dialog shown when QR code pairing failed.
*/
int MODE_QRCODE_FAILED = 3;
/**
* Gets the context for the dialog.
*
* @return the context for the dialog
*/
Context getContext();
/**
* Gets the controller for the dialog.
*
* @return the controller for the dialog.
*/
AdbWirelessDialogController getController();
/**
* Gets the layout for the dialog.
*
* @return the {@link LayoutInflater} for the dialog
*/
LayoutInflater getLayoutInflater();
/**
* Gets the dialog mode/ID.
*
* @return the mode of the dialog
*/
int getMode();
/**
* Sends a submit command to the dialog.
*/
void dispatchSubmit();
/**
* Enables if user can cancel a dialog by clicking outside of the dialog.
*
* @param cancel The flag indicating if can cancel by clicking outside
*/
void setCanceledOnTouchOutside(boolean cancel);
/**
* Sets the title of the dialog.
*
* @param id the string id
*/
void setTitle(int id);
/**
* Sets the title of the dialog.
*
* @param title the title string
*/
void setTitle(CharSequence title);
/**
* Sets the text for the submit button.
*
* @param text the submit text
*/
void setSubmitButton(CharSequence text);
/**
* Sets the text for the cancel button.
*
* @param text the cancel text
*/
void setCancelButton(CharSequence text);
/**
* Gets the button widget for the submit button.
*
* @return the submit {@link Button} widget
*/
Button getSubmitButton();
/**
* Gets the button widget for the cancel button.
*
* @return the cancel {@link Button} widget
*/
Button getCancelButton();
}

View File

@@ -443,6 +443,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
controllers.add(new SelectDSUPreferenceController(context));
controllers.add(new AdbPreferenceController(context, fragment));
controllers.add(new ClearAdbKeysPreferenceController(context, fragment));
controllers.add(new WirelessDebuggingPreferenceController(context, lifecycle));
controllers.add(new LocalTerminalPreferenceController(context));
controllers.add(new BugReportInPowerPreferenceController(context));
controllers.add(new AutomaticSystemServerHeapDumpPreferenceController(context));

View File

@@ -0,0 +1,140 @@
/*
* 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.settings.development;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import com.android.settings.widget.SwitchWidgetController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
/**
* Class to control the switch bar in the wireless debugging fragment.
*/
public class WirelessDebuggingEnabler implements SwitchWidgetController.OnSwitchChangeListener,
LifecycleObserver, OnResume, OnPause {
private static final String TAG = "WirelessDebuggingEnabler";
private final SwitchWidgetController mSwitchWidget;
private Context mContext;
private boolean mListeningToOnSwitchChange = false;
private OnEnabledListener mListener;
private final ContentResolver mContentResolver;
private final ContentObserver mSettingsObserver;
private final Handler mHandler = new Handler(Looper.getMainLooper());
public WirelessDebuggingEnabler(Context context, SwitchWidgetController switchWidget,
OnEnabledListener listener, Lifecycle lifecycle) {
mContext = context;
mSwitchWidget = switchWidget;
mSwitchWidget.setListener(this);
mSwitchWidget.setupView();
mListener = listener;
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mContentResolver = context.getContentResolver();
mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
Log.i(TAG, "ADB_WIFI_ENABLED=" + isAdbWifiEnabled());
onWirelessDebuggingEnabled(isAdbWifiEnabled());
}
};
}
private boolean isAdbWifiEnabled() {
return Settings.Global.getInt(mContentResolver, Settings.Global.ADB_WIFI_ENABLED,
AdbPreferenceController.ADB_SETTING_OFF)
!= AdbPreferenceController.ADB_SETTING_OFF;
}
/**
* Tears down the switch controller for the wireless debugging switch.
*/
public void teardownSwitchController() {
if (mListeningToOnSwitchChange) {
mSwitchWidget.stopListening();
mListeningToOnSwitchChange = false;
}
mSwitchWidget.teardownView();
}
@Override
public void onResume() {
if (!mListeningToOnSwitchChange) {
mSwitchWidget.startListening();
mListeningToOnSwitchChange = true;
}
onWirelessDebuggingEnabled(isAdbWifiEnabled());
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED), false,
mSettingsObserver);
}
@Override
public void onPause() {
if (mListeningToOnSwitchChange) {
mSwitchWidget.stopListening();
mListeningToOnSwitchChange = false;
}
mContentResolver.unregisterContentObserver(mSettingsObserver);
}
private void onWirelessDebuggingEnabled(boolean enabled) {
mSwitchWidget.setChecked(enabled);
if (mListener != null) {
mListener.onEnabled(enabled);
}
}
protected void writeAdbWifiSetting(boolean enabled) {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ADB_WIFI_ENABLED, enabled ? AdbPreferenceController.ADB_SETTING_ON
: AdbPreferenceController.ADB_SETTING_OFF);
}
@Override
public boolean onSwitchToggled(boolean isChecked) {
writeAdbWifiSetting(isChecked);
return true;
}
/**
* Interface for subscribers to implement in order to listen for
* wireless debugging state changes.
*/
public interface OnEnabledListener {
/**
* Called when wireless debugging state changes.
*
* @param enabled the state of wireless debugging
*/
void onEnabled(boolean enabled);
}
}

View File

@@ -0,0 +1,495 @@
/*
* 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.settings.development;
import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.debug.AdbManager;
import android.debug.IAdbManager;
import android.debug.PairDevice;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SwitchBarController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.FooterPreference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Fragment shown when clicking in the "Wireless Debugging" preference in
* the developer options.
*/
@SearchIndexable
public class WirelessDebuggingFragment extends DashboardFragment
implements WirelessDebuggingEnabler.OnEnabledListener {
private static final String TAG = "WirelessDebuggingFrag";
// Activity result from clicking on a paired device.
private static final int PAIRED_DEVICE_REQUEST = 0;
public static final String PAIRED_DEVICE_REQUEST_TYPE = "request_type";
public static final int FORGET_ACTION = 0;
// Activity result from pairing a device.
private static final int PAIRING_DEVICE_REQUEST = 1;
public static final String PAIRING_DEVICE_REQUEST_TYPE = "request_type_pairing";
public static final int SUCCESS_ACTION = 0;
public static final int FAIL_ACTION = 1;
public static final String PAIRED_DEVICE_EXTRA = "paired_device";
public static final String DEVICE_NAME_EXTRA = "device_name";
public static final String IP_ADDR_EXTRA = "ip_addr";
private WirelessDebuggingEnabler mWifiDebuggingEnabler;
private static AdbIpAddressPreferenceController sAdbIpAddressPreferenceController;
// UI components
private static final String PREF_KEY_ADB_DEVICE_NAME = "adb_device_name_pref";
private static final String PREF_KEY_ADB_IP_ADDR = "adb_ip_addr_pref";
private static final String PREF_KEY_PAIRING_METHODS_CATEGORY = "adb_pairing_methods_category";
private static final String PREF_KEY_ADB_QRCODE_PAIRING = "adb_pair_method_qrcode_pref";
private static final String PREF_KEY_ADB_CODE_PAIRING = "adb_pair_method_code_pref";
private static final String PREF_KEY_PAIRED_DEVICES_CATEGORY = "adb_paired_devices_category";
private static final String PREF_KEY_FOOTER_CATEGORY = "adb_wireless_footer_category";
private Preference mDeviceNamePreference;
private Preference mIpAddrPreference;
private PreferenceCategory mPairingMethodsCategory;
private Preference mQrcodePairingPreference;
private Preference mCodePairingPreference;
private PreferenceCategory mPairedDevicesCategory;
private PreferenceCategory mFooterCategory;
private FooterPreference mOffMessagePreference;
// Map of paired devices, with the device GUID is the key
private Map<String, AdbPairedDevicePreference> mPairedDevicePreferences;
private IAdbManager mAdbManager;
private int mConnectionPort;
class PairingCodeDialogListener implements AdbWirelessDialog.AdbWirelessDialogListener {
@Override
public void onDismiss() {
Log.i(TAG, "onDismiss");
mPairingCodeDialog = null;
try {
mAdbManager.disablePairing();
} catch (RemoteException e) {
Log.e(TAG, "Unable to cancel pairing");
}
}
}
final PairingCodeDialogListener mPairingCodeDialogListener = new PairingCodeDialogListener();
AdbWirelessDialog mPairingCodeDialog;
private IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION.equals(action)) {
Map<String, PairDevice> newPairedDevicesList =
(HashMap<String, PairDevice>) intent.getSerializableExtra(
AdbManager.WIRELESS_DEVICES_EXTRA);
updatePairedDevicePreferences(newPairedDevicesList);
} else if (AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION.equals(action)) {
int status = intent.getIntExtra(AdbManager.WIRELESS_STATUS_EXTRA,
AdbManager.WIRELESS_STATUS_DISCONNECTED);
if (status == AdbManager.WIRELESS_STATUS_CONNECTED) {
int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0);
Log.i(TAG, "Got adbwifi port=" + port);
} else {
Log.i(TAG, "adbwifi server disconnected");
}
} else if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) {
Integer res = intent.getIntExtra(
AdbManager.WIRELESS_STATUS_EXTRA,
AdbManager.WIRELESS_STATUS_FAIL);
if (res.equals(AdbManager.WIRELESS_STATUS_PAIRING_CODE)) {
String pairingCode = intent.getStringExtra(
AdbManager.WIRELESS_PAIRING_CODE_EXTRA);
if (mPairingCodeDialog != null) {
mPairingCodeDialog.getController().setPairingCode(pairingCode);
}
} else if (res.equals(AdbManager.WIRELESS_STATUS_SUCCESS)) {
removeDialog(AdbWirelessDialogUiBase.MODE_PAIRING);
mPairingCodeDialog = null;
} else if (res.equals(AdbManager.WIRELESS_STATUS_FAIL)) {
removeDialog(AdbWirelessDialogUiBase.MODE_PAIRING);
mPairingCodeDialog = null;
showDialog(AdbWirelessDialogUiBase.MODE_PAIRING_FAILED);
} else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) {
int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0);
Log.i(TAG, "Got pairing code port=" + port);
String ipAddr = sAdbIpAddressPreferenceController.getIpv4Address() + ":" + port;
if (mPairingCodeDialog != null) {
mPairingCodeDialog.getController().setIpAddr(ipAddr);
}
}
}
}
};
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final SettingsActivity activity = (SettingsActivity) getActivity();
mWifiDebuggingEnabler = new WirelessDebuggingEnabler(activity,
new SwitchBarController(activity.getSwitchBar()), this,
getSettingsLifecycle());
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferences();
mIntentFilter = new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION);
mIntentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION);
mIntentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION);
}
private void addPreferences() {
mDeviceNamePreference =
(Preference) findPreference(PREF_KEY_ADB_DEVICE_NAME);
mIpAddrPreference =
(Preference) findPreference(PREF_KEY_ADB_IP_ADDR);
mPairingMethodsCategory =
(PreferenceCategory) findPreference(PREF_KEY_PAIRING_METHODS_CATEGORY);
mCodePairingPreference =
(Preference) findPreference(PREF_KEY_ADB_CODE_PAIRING);
mCodePairingPreference.setOnPreferenceClickListener(preference -> {
showDialog(AdbWirelessDialogUiBase.MODE_PAIRING);
return true;
});
mQrcodePairingPreference =
(Preference) findPreference(PREF_KEY_ADB_QRCODE_PAIRING);
mQrcodePairingPreference.setOnPreferenceClickListener(preference -> {
launchQrcodeScannerFragment();
return true;
});
mPairedDevicesCategory =
(PreferenceCategory) findPreference(PREF_KEY_PAIRED_DEVICES_CATEGORY);
mFooterCategory =
(PreferenceCategory) findPreference(PREF_KEY_FOOTER_CATEGORY);
mOffMessagePreference =
new FooterPreference(mFooterCategory.getContext());
final CharSequence title = getText(R.string.adb_wireless_list_empty_off);
mOffMessagePreference.setTitle(title);
mFooterCategory.addPreference(mOffMessagePreference);
}
@Override
public void onDestroyView() {
super.onDestroyView();
mWifiDebuggingEnabler.teardownSwitchController();
}
@Override
public void onResume() {
super.onResume();
getActivity().registerReceiver(mReceiver, mIntentFilter);
}
@Override
public void onPause() {
super.onPause();
removeDialog(AdbWirelessDialogUiBase.MODE_PAIRING);
getActivity().unregisterReceiver(mReceiver);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PAIRED_DEVICE_REQUEST) {
handlePairedDeviceRequest(resultCode, data);
} else if (requestCode == PAIRING_DEVICE_REQUEST) {
handlePairingDeviceRequest(resultCode, data);
}
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_ADB_WIRELESS;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
return SettingsEnums.ADB_WIRELESS_DEVICE_PAIRING_DIALOG;
}
@Override
public Dialog onCreateDialog(int dialogId) {
Dialog d = AdbWirelessDialog.createModal(getActivity(),
dialogId == AdbWirelessDialogUiBase.MODE_PAIRING
? mPairingCodeDialogListener : null, dialogId);
if (dialogId == AdbWirelessDialogUiBase.MODE_PAIRING) {
mPairingCodeDialog = (AdbWirelessDialog) d;
try {
mAdbManager.enablePairingByPairingCode();
} catch (RemoteException e) {
Log.e(TAG, "Unable to enable pairing");
mPairingCodeDialog = null;
d = AdbWirelessDialog.createModal(getActivity(), null,
AdbWirelessDialogUiBase.MODE_PAIRING_FAILED);
}
}
if (d != null) {
return d;
}
return super.onCreateDialog(dialogId);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.adb_wireless_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getActivity(), this /* fragment */,
getSettingsLifecycle());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(
Context context, Activity activity, WirelessDebuggingFragment fragment,
Lifecycle lifecycle) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
sAdbIpAddressPreferenceController =
new AdbIpAddressPreferenceController(context, lifecycle);
controllers.add(sAdbIpAddressPreferenceController);
return controllers;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public void onEnabled(boolean enabled) {
if (enabled) {
showDebuggingPreferences();
mAdbManager = IAdbManager.Stub.asInterface(ServiceManager.getService(
Context.ADB_SERVICE));
try {
Map<String, PairDevice> newList = mAdbManager.getPairedDevices();
updatePairedDevicePreferences(newList);
mConnectionPort = mAdbManager.getAdbWirelessPort();
if (mConnectionPort > 0) {
Log.i(TAG, "onEnabled(): connect_port=" + mConnectionPort);
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to request the paired list for Adb wireless");
}
sAdbIpAddressPreferenceController.updateState(mIpAddrPreference);
} else {
showOffMessage();
}
}
private void showOffMessage() {
mDeviceNamePreference.setVisible(false);
mIpAddrPreference.setVisible(false);
mPairingMethodsCategory.setVisible(false);
mPairedDevicesCategory.setVisible(false);
mFooterCategory.setVisible(true);
}
private void showDebuggingPreferences() {
mDeviceNamePreference.setVisible(true);
mIpAddrPreference.setVisible(true);
mPairingMethodsCategory.setVisible(true);
mPairedDevicesCategory.setVisible(true);
mFooterCategory.setVisible(false);
}
private void updatePairedDevicePreferences(Map<String, PairDevice> newList) {
// TODO(joshuaduong): Move the non-UI stuff into another thread
// as the processing could take some time.
if (newList == null) {
mPairedDevicesCategory.removeAll();
return;
}
if (mPairedDevicePreferences == null) {
mPairedDevicePreferences = new HashMap<String, AdbPairedDevicePreference>();
}
if (mPairedDevicePreferences.isEmpty()) {
for (Map.Entry<String, PairDevice> entry : newList.entrySet()) {
AdbPairedDevicePreference p =
new AdbPairedDevicePreference(entry.getValue(),
mPairedDevicesCategory.getContext());
mPairedDevicePreferences.put(
entry.getKey(),
p);
p.setOnPreferenceClickListener(preference -> {
AdbPairedDevicePreference pref =
(AdbPairedDevicePreference) preference;
launchPairedDeviceDetailsFragment(pref);
return true;
});
mPairedDevicesCategory.addPreference(p);
}
} else {
// Remove any devices no longer on the newList
mPairedDevicePreferences.entrySet().removeIf(entry -> {
if (newList.get(entry.getKey()) == null) {
mPairedDevicesCategory.removePreference(entry.getValue());
return true;
} else {
// It is in the newList. Just update the PairDevice value
AdbPairedDevicePreference p =
entry.getValue();
p.setPairedDevice(newList.get(entry.getKey()));
p.refresh();
return false;
}
});
// Add new devices if any.
for (Map.Entry<String, PairDevice> entry :
newList.entrySet()) {
if (mPairedDevicePreferences.get(entry.getKey()) == null) {
AdbPairedDevicePreference p =
new AdbPairedDevicePreference(entry.getValue(),
mPairedDevicesCategory.getContext());
mPairedDevicePreferences.put(
entry.getKey(),
p);
p.setOnPreferenceClickListener(preference -> {
AdbPairedDevicePreference pref =
(AdbPairedDevicePreference) preference;
launchPairedDeviceDetailsFragment(pref);
return true;
});
mPairedDevicesCategory.addPreference(p);
}
}
}
}
private void launchPairedDeviceDetailsFragment(AdbPairedDevicePreference p) {
// For sending to the device details fragment.
p.savePairedDeviceToExtras(p.getExtras());
new SubSettingLauncher(getContext())
.setTitleRes(R.string.adb_wireless_device_details_title)
.setDestination(AdbDeviceDetailsFragment.class.getName())
.setArguments(p.getExtras())
.setSourceMetricsCategory(getMetricsCategory())
.setResultListener(this, PAIRED_DEVICE_REQUEST)
.launch();
}
void handlePairedDeviceRequest(int result, Intent data) {
if (result != Activity.RESULT_OK) {
return;
}
Log.i(TAG, "Processing paired device request");
int requestType = data.getIntExtra(PAIRED_DEVICE_REQUEST_TYPE, -1);
PairDevice p;
switch (requestType) {
case FORGET_ACTION:
try {
p = (PairDevice) data.getParcelableExtra(PAIRED_DEVICE_EXTRA);
mAdbManager.unpairDevice(p.getGuid());
} catch (RemoteException e) {
Log.e(TAG, "Unable to forget the device");
}
break;
default:
break;
}
}
void handlePairingDeviceRequest(int result, Intent data) {
if (result != Activity.RESULT_OK) {
return;
}
int requestType = data.getIntExtra(PAIRING_DEVICE_REQUEST_TYPE, -1);
switch (requestType) {
case FAIL_ACTION:
showDialog(AdbWirelessDialogUiBase.MODE_PAIRING_FAILED);
break;
default:
break;
}
}
private String getDeviceName() {
// Keep device name in sync with Settings > About phone > Device name
String deviceName = Settings.Global.getString(getContext().getContentResolver(),
Settings.Global.DEVICE_NAME);
if (deviceName == null) {
deviceName = Build.MODEL;
}
return deviceName;
}
private void launchQrcodeScannerFragment() {
new SubSettingLauncher(getContext())
.setDestination(AdbQrcodeScannerFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.setResultListener(this, PAIRING_DEVICE_REQUEST)
.launch();
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.development_tile_settings) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
}
};
}

View File

@@ -0,0 +1,143 @@
/*
* 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.settings.development;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.debug.IAdbManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.MasterSwitchPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.development.DeveloperOptionsPreferenceController;
/**
* This controls the master switch controller in the developer options page for
* "Wireless debugging".
*/
public class WirelessDebuggingPreferenceController extends DeveloperOptionsPreferenceController
implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin,
LifecycleObserver, OnResume, OnPause {
private static final String TAG = "WirelessDebugPrefCtrl";
private final IAdbManager mAdbManager;
private final ContentResolver mContentResolver;
private final ContentObserver mSettingsObserver;
private final Handler mHandler = new Handler(Looper.getMainLooper());
public static final String KEY_TOGGLE_ADB_WIRELESS = "toggle_adb_wireless";
public WirelessDebuggingPreferenceController(Context context, Lifecycle lifecycle) {
super(context);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mAdbManager = IAdbManager.Stub.asInterface(ServiceManager.getService(Context.ADB_SERVICE));
mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateState(mPreference);
}
};
mContentResolver = context.getContentResolver();
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
}
@Override
public boolean isAvailable() {
try {
return mAdbManager.isAdbWifiSupported();
} catch (RemoteException e) {
Log.e(TAG, "Unable to check if adb wifi is supported.", e);
}
return false;
}
@Override
public String getPreferenceKey() {
return KEY_TOGGLE_ADB_WIRELESS;
}
/**
* Called when developer options is enabled and the preference is available
*/
@Override
protected void onDeveloperOptionsSwitchEnabled() {
super.onDeveloperOptionsSwitchEnabled();
mPreference.setEnabled(true);
}
/**
* Called when developer options is disabled and the preference is available
*/
@Override
protected void onDeveloperOptionsSwitchDisabled() {
super.onDeveloperOptionsSwitchDisabled();
mPreference.setEnabled(false);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ADB_WIFI_ENABLED,
AdbPreferenceController.ADB_SETTING_OFF);
}
@Override
public void onResume() {
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED), false,
mSettingsObserver);
}
@Override
public void onPause() {
mContentResolver.unregisterContentObserver(mSettingsObserver);
}
@Override
public void updateState(Preference preference) {
boolean enabled = Settings.Global.getInt(mContentResolver,
Settings.Global.ADB_WIFI_ENABLED, AdbPreferenceController.ADB_SETTING_OFF)
!= AdbPreferenceController.ADB_SETTING_OFF;
((MasterSwitchPreference) preference).setChecked(enabled);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean enabled = (Boolean) newValue;
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ADB_WIFI_ENABLED,
enabled ? AdbPreferenceController.ADB_SETTING_ON
: AdbPreferenceController.ADB_SETTING_OFF);
return true;
}
}