/* * Copyright (C) 2012 The Android Open Source Project * Copyright (C) 2016 The CyanogenMod 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.cyanogenmod; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.RadioButton; import android.widget.Switch; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.widget.SwitchBar; import cyanogenmod.app.LiveLockScreenManager; import cyanogenmod.externalviews.KeyguardExternalViewProviderService; import cyanogenmod.providers.CMSettings; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import static cyanogenmod.providers.CMSettings.Secure.LIVE_LOCK_SCREEN_ENABLED; public class LiveLockScreenSettings extends SettingsPreferenceFragment implements SwitchBar.OnSwitchChangeListener { private static final String TAG = LiveLockScreenSettings.class.getSimpleName(); static final boolean DEBUG = false; private static final String PACKAGE_SCHEME = "package"; private final PackageReceiver mPackageReceiver = new PackageReceiver(); private Context mContext; private LiveLockScreenBackend mBackend; private LiveLockScreenInfoAdapter mAdapter; private SwitchBar mSwitchBar; private boolean mRefreshing; @Override public void onAttach(Activity activity) { logd("onAttach(%s)", activity.getClass().getSimpleName()); super.onAttach(activity); mContext = activity; } @Override protected int getMetricsCategory() { return MetricsLogger.LOCKSCREEN; } @Override public void onCreate(Bundle icicle) { logd("onCreate(%s)", icicle); super.onCreate(icicle); mBackend = new LiveLockScreenBackend(getActivity()); } @Override public void onSwitchChanged(Switch switchView, boolean isChecked) { if (!mRefreshing) { mBackend.setEnabled(isChecked); refreshFromBackend(); } } @Override public void onStart() { logd("onStart()"); super.onStart(); } @Override public void onDestroyView() { logd("onDestroyView()"); super.onDestroyView(); mSwitchBar.removeOnSwitchChangeListener(this); mSwitchBar.hide(); } @Override public void onActivityCreated(Bundle savedInstanceState) { logd("onActivityCreated(%s)", savedInstanceState); super.onActivityCreated(savedInstanceState); ListView listView = getListView(); listView.setItemsCanFocus(true); TextView emptyView = (TextView) getView().findViewById(android.R.id.empty); emptyView.setText(R.string.live_lock_screen_settings_disabled_prompt); listView.setEmptyView(emptyView); mAdapter = new LiveLockScreenInfoAdapter(mContext); listView.setAdapter(mAdapter); final SettingsActivity sa = (SettingsActivity) getActivity(); mSwitchBar = sa.getSwitchBar(); mSwitchBar.addOnSwitchChangeListener(this); mSwitchBar.show(); } @Override public void onPause() { logd("onPause()"); super.onPause(); mContext.unregisterReceiver(mPackageReceiver); } @Override public void onResume() { logd("onResume()"); super.onResume(); refreshFromBackend(); // listen for package changes IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addDataScheme(PACKAGE_SCHEME); mContext.registerReceiver(mPackageReceiver , filter); } private void refreshFromBackend() { logd("refreshFromBackend()"); mRefreshing = true; boolean liveLockScreenEnabled = mBackend.isEnabled(); if (mSwitchBar.isChecked() != liveLockScreenEnabled) { mSwitchBar.setChecked(liveLockScreenEnabled); } mAdapter.clear(); if (liveLockScreenEnabled) { List liveLockScreenInfos = mBackend.getLiveLockScreenInfos(); mAdapter.addAll(liveLockScreenInfos); } mRefreshing = false; } private static void logd(String msg, Object... args) { if (DEBUG) { Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); } } private class LiveLockScreenInfoAdapter extends ArrayAdapter { private final LayoutInflater mInflater; public LiveLockScreenInfoAdapter(Context context) { super(context, 0); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public View getView(int position, View convertView, ViewGroup parent) { LiveLockScreenBackend.LiveLockScreenInfo liveLockScreenInfo = getItem(position); logd("getView(%s)", liveLockScreenInfo.caption); final View row = convertView != null ? convertView : createLiveLockScreenInfoRow(parent); row.setTag(liveLockScreenInfo); // bind icon ((ImageView) row.findViewById(android.R.id.icon)) .setImageDrawable(liveLockScreenInfo.icon); // bind caption ((TextView) row.findViewById(android.R.id.title)).setText(liveLockScreenInfo.caption); // bind radio button RadioButton radioButton = (RadioButton) row.findViewById(R.id.radio); radioButton.setChecked(liveLockScreenInfo.isActive); radioButton.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { row.onTouchEvent(event); return false; }}); // bind settings button + divider boolean showSettings = liveLockScreenInfo.settingsComponentName != null; View settingsDivider = row.findViewById(R.id.divider); settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); ImageView settingsButton = (ImageView) row.findViewById(R.id.settings); settingsButton.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE); settingsButton.setAlpha(liveLockScreenInfo.isActive ? 1f : Utils.DISABLED_ALPHA); settingsButton.setEnabled(liveLockScreenInfo.isActive); settingsButton.setFocusable(liveLockScreenInfo.isActive); settingsButton.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { mBackend.launchSettings( (LiveLockScreenBackend.LiveLockScreenInfo) row.getTag()); }}); return row; } private View createLiveLockScreenInfoRow(ViewGroup parent) { final View row = mInflater.inflate(R.layout.live_lock_screen_info_row, parent, false); row.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { v.setPressed(true); activate((LiveLockScreenBackend.LiveLockScreenInfo) row.getTag()); }}); return row; } private LiveLockScreenBackend.LiveLockScreenInfo getCurrentSelection() { for (int i = 0; i < getCount(); i++) { LiveLockScreenBackend.LiveLockScreenInfo liveLockScreenInfo = getItem(i); if (liveLockScreenInfo.isActive) { return liveLockScreenInfo; } } return null; } private void activate(LiveLockScreenBackend.LiveLockScreenInfo liveLockScreenInfo) { if (liveLockScreenInfo.equals(getCurrentSelection())) { return; } for (int i = 0; i < getCount(); i++) { getItem(i).isActive = false; } liveLockScreenInfo.isActive = true; mBackend.setActiveLiveLockScreen(liveLockScreenInfo.componentName); if (liveLockScreenInfo.settingsComponentName != null) { mBackend.launchSettings(liveLockScreenInfo); } notifyDataSetChanged(); } } private class PackageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { logd("PackageReceiver.onReceive"); refreshFromBackend(); } } public static class LiveLockScreenBackend { private static final String TAG = LiveLockScreenSettings.class.getSimpleName() + ".Backend"; public static class LiveLockScreenInfo { CharSequence caption; Drawable icon; boolean isActive; public ComponentName componentName; public ComponentName settingsComponentName; @Override public String toString() { StringBuilder sb = new StringBuilder(LiveLockScreenInfo.class.getSimpleName()); sb.append('[').append(caption); if (isActive) { sb.append(",active"); } sb.append(',').append(componentName); if (settingsComponentName != null) { sb.append("settings=").append(settingsComponentName); } return sb.append(']').toString(); } } private final Context mContext; private final LiveLockScreenInfoComparator mComparator; private LiveLockScreenManager mLLSM; public LiveLockScreenBackend(Context context) { mContext = context; mComparator = new LiveLockScreenInfoComparator(null); mLLSM = LiveLockScreenManager.getInstance(context); } public List getLiveLockScreenInfos() { logd("getLiveLockScreenInfos()"); ComponentName activeLiveLockScreen = getActiveLiveLockScreen(); PackageManager pm = mContext.getPackageManager(); Intent liveLockScreenIntent = new Intent(KeyguardExternalViewProviderService.SERVICE_INTERFACE); List resolveInfos = pm.queryIntentServices(liveLockScreenIntent, PackageManager.GET_META_DATA); List liveLockScreenInfos = new ArrayList<>(resolveInfos.size()); for (ResolveInfo resolveInfo : resolveInfos) { if (resolveInfo.serviceInfo == null) { continue; } LiveLockScreenInfo liveLockScreenInfo = new LiveLockScreenInfo(); liveLockScreenInfo.caption = resolveInfo.loadLabel(pm); liveLockScreenInfo.icon = resolveInfo.loadIcon(pm); liveLockScreenInfo.componentName = getLiveLockScreenComponentName(resolveInfo); liveLockScreenInfo.isActive = liveLockScreenInfo.componentName.equals( activeLiveLockScreen); liveLockScreenInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo); liveLockScreenInfos.add(liveLockScreenInfo); } Collections.sort(liveLockScreenInfos, mComparator); return liveLockScreenInfos; } public CharSequence getActiveLiveLockScreenName() { ComponentName cn = getActiveLiveLockScreen(); if (cn != null) { PackageManager pm = mContext.getPackageManager(); try { ServiceInfo ri = pm.getServiceInfo(cn, 0); if (ri != null) { return ri.loadLabel(pm); } } catch (PackageManager.NameNotFoundException exc) { return null; // uninstalled? } } return null; } public boolean isEnabled() { return getBoolean(LIVE_LOCK_SCREEN_ENABLED, false); } public void setEnabled(boolean value) { logd("setEnabled(%s)", value); setBoolean(LIVE_LOCK_SCREEN_ENABLED, value); } private boolean getBoolean(String key, boolean def) { return CMSettings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1; } private void setBoolean(String key, boolean value) { mLLSM.setLiveLockScreenEnabled(value); } public void setActiveLiveLockScreen(ComponentName liveLockScreen) { logd("setActiveLiveLockScreen(%s)", liveLockScreen); cyanogenmod.app.LiveLockScreenInfo.Builder builder = new cyanogenmod.app.LiveLockScreenInfo.Builder(); builder.setComponent(liveLockScreen); mLLSM.setDefaultLiveLockScreen(builder.build()); } public ComponentName getActiveLiveLockScreen() { cyanogenmod.app.LiveLockScreenInfo llsInfo = mLLSM.getDefaultLiveLockScreen(); return llsInfo != null ? llsInfo.component : null; } public void launchSettings(LiveLockScreenInfo liveLockScreenInfo) { logd("launchSettings(%s)", liveLockScreenInfo); if (liveLockScreenInfo == null || liveLockScreenInfo.settingsComponentName == null) { return; } mContext.startActivity(new Intent().setComponent( liveLockScreenInfo.settingsComponentName)); } private static ComponentName getLiveLockScreenComponentName(ResolveInfo resolveInfo) { if (resolveInfo == null || resolveInfo.serviceInfo == null) { return null; } return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); } private static ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) { if (resolveInfo == null || resolveInfo.serviceInfo == null || resolveInfo.serviceInfo.metaData == null) { return null; } String cn = null; XmlResourceParser parser = null; Exception caughtException = null; try { parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, KeyguardExternalViewProviderService.META_DATA); if (parser == null) { Log.w(TAG, "No " + KeyguardExternalViewProviderService.META_DATA + " meta-data"); return null; } Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo); AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { } String nodeName = parser.getName(); if (!"lockscreen".equals(nodeName)) { Log.w(TAG, "Meta-data does not start with lockscreen tag"); return null; } // Dream styleable has the attributes we need so we'll piggy back off of that TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream); cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity); sa.recycle(); } catch (PackageManager.NameNotFoundException e) { caughtException = e; } catch (IOException e) { caughtException = e; } catch (XmlPullParserException e) { caughtException = e; } finally { if (parser != null) parser.close(); } if (caughtException != null) { Log.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException); return null; } if (cn != null && cn.indexOf('/') < 0) { cn = resolveInfo.serviceInfo.packageName + "/" + cn; } return cn == null ? null : ComponentName.unflattenFromString(cn); } private static void logd(String msg, Object... args) { if (LiveLockScreenSettings.DEBUG) { Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); } } private static class LiveLockScreenInfoComparator implements Comparator { private final ComponentName mDefaultLiveLockScreen; public LiveLockScreenInfoComparator(ComponentName defaultLiveLockScreen) { mDefaultLiveLockScreen = defaultLiveLockScreen; } @Override public int compare(LiveLockScreenInfo lhs, LiveLockScreenInfo rhs) { return sortKey(lhs).compareTo(sortKey(rhs)); } private String sortKey(LiveLockScreenInfo di) { StringBuilder sb = new StringBuilder(); sb.append(di.componentName.equals(mDefaultLiveLockScreen) ? '0' : '1'); sb.append(di.caption); return sb.toString(); } } } }