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

Commit 2388251f authored by Adam Powell's avatar Adam Powell
Browse files

Pinning components in ChooserActivity, take 2

Keep the ChooserActivity in the android:ui process/system.

Change-Id: Ia3ae29a8d629f41a53a066b9fc7ed0b28666b261
parent 56190d5e
Loading
Loading
Loading
Loading
+64 −9
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
@@ -35,6 +36,7 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -68,6 +70,7 @@ import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -91,6 +94,11 @@ public class ChooserActivity extends ResolverActivity {
    private ChooserListAdapter mChooserListAdapter;
    private ChooserRowAdapter mChooserRowAdapter;

    private SharedPreferences mPinnedSharedPrefs;
    private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
    private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
    private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";

    private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();

    private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
@@ -207,12 +215,30 @@ public class ChooserActivity extends ResolverActivity {
        mRefinementIntentSender = intent.getParcelableExtra(
                Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
        setSafeForwardingMode(true);

        mPinnedSharedPrefs = getPinnedSharedPrefs(this);
        super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                null, false);

        MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
    }

    static SharedPreferences getPinnedSharedPrefs(Context context) {
        // The code below is because in the android:ui process, no one can hear you scream.
        // The package info in the context isn't initialized in the way it is for normal apps,
        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
        // build the path manually below using the same policy that appears in ContextImpl.
        // This fails silently under the hood if there's a problem, so if we find ourselves in
        // the case where we don't have access to credential encrypted storage we just won't
        // have our pinned target info.
        final File prefsFile = new File(new File(
                Environment.getDataUserCredentialEncryptedPackageDirectory(null,
                        context.getUserId(), context.getPackageName()),
                "shared_prefs"),
                PINNED_SHARED_PREFS_NAME + ".xml");
        return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
@@ -243,7 +269,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    void onActivityStarted(TargetInfo cti) {
    public void onActivityStarted(TargetInfo cti) {
        if (mChosenComponentSender != null) {
            final ComponentName target = cti.getResolvedComponentName();
            if (target != null) {
@@ -259,7 +285,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
            boolean alwaysUseOption) {
        final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
        mChooserListAdapter = (ChooserListAdapter) adapter;
@@ -272,17 +298,17 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    int getLayoutResource() {
    public int getLayoutResource() {
        return R.layout.chooser_grid;
    }

    @Override
    boolean shouldGetActivityMetadata() {
    public boolean shouldGetActivityMetadata() {
        return true;
    }

    @Override
    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
        final Intent intent = target.getResolvedIntent();
        final ResolveInfo resolve = target.getResolveInfo();

@@ -299,6 +325,16 @@ public class ChooserActivity extends ResolverActivity {
        return false;
    }

    @Override
    public void showTargetDetails(ResolveInfo ri) {
        ComponentName name = ri.activityInfo.getComponentName();
        boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
        ResolverTargetActionsDialogFragment f =
                new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
                        name, pinned);
        f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
    }

    private void modifyTargetIntent(Intent in) {
        final String action = in.getAction();
        if (Intent.ACTION_SEND.equals(action) ||
@@ -340,7 +376,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    void startSelected(int which, boolean always, boolean filtered) {
    public void startSelected(int which, boolean always, boolean filtered) {
        super.startSelected(which, always, filtered);

        if (mChooserListAdapter != null) {
@@ -471,7 +507,7 @@ public class ChooserActivity extends ResolverActivity {
        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
    }

    void onSetupVoiceInteraction() {
    public void onSetupVoiceInteraction() {
        // Do nothing. We'll send the voice stuff ourselves.
    }

@@ -543,7 +579,7 @@ public class ChooserActivity extends ResolverActivity {
    }

    @Override
    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
            boolean filterLastUsed) {
        final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
@@ -711,6 +747,11 @@ public class ChooserActivity extends ResolverActivity {
            }
            return results;
        }

        @Override
        public boolean isPinned() {
            return mSourceInfo != null ? mSourceInfo.isPinned() : false;
        }
    }

    public class ChooserListAdapter extends ResolveListAdapter {
@@ -776,6 +817,20 @@ public class ChooserActivity extends ResolverActivity {
            return false;
        }

        @Override
        public boolean isComponentPinned(ComponentName name) {
            return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
        }

        @Override
        public float getScore(DisplayResolveInfo target) {
            float score = super.getScore(target);
            if (target.isPinned()) {
                score += PINNED_TARGET_SCORE_BOOST;
            }
            return score;
        }

        @Override
        public View onCreateView(ViewGroup parent) {
            return mInflater.inflate(
@@ -1121,7 +1176,7 @@ public class ChooserActivity extends ResolverActivity {
                v.setOnLongClickListener(new OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        showAppDetails(
                        showTargetDetails(
                                mChooserListAdapter.resolveInfoForPosition(
                                        holder.itemIndices[column], true));
                        return true;
+68 −34
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
import android.content.pm.ComponentInfo;
import android.os.AsyncTask;
import android.provider.Settings;
import android.text.TextUtils;
@@ -336,12 +337,12 @@ public class ResolverActivity extends Activity {
    /**
     * Perform any initialization needed for voice interaction.
     */
    void onSetupVoiceInteraction() {
    public void onSetupVoiceInteraction() {
        // Do it right now. Subclasses may delay this and send it later.
        sendVoiceChoicesIfNeeded();
    }

    void sendVoiceChoicesIfNeeded() {
    public void sendVoiceChoicesIfNeeded() {
        if (!isVoiceInteraction()) {
            // Clearly not needed.
            return;
@@ -382,7 +383,7 @@ public class ResolverActivity extends Activity {
        return null;
    }

    int getLayoutResource() {
    public int getLayoutResource() {
        return R.layout.resolver_list;
    }

@@ -591,7 +592,7 @@ public class ResolverActivity extends Activity {
                mAlwaysUseOption);
    }

    void startSelected(int which, boolean always, boolean filtered) {
    public void startSelected(int which, boolean always, boolean filtered) {
        if (isFinishing()) {
            return;
        }
@@ -761,7 +762,7 @@ public class ResolverActivity extends Activity {
        return true;
    }

    void safelyStartActivity(TargetInfo cti) {
    public void safelyStartActivity(TargetInfo cti) {
        // If needed, show that intent is forwarded
        // from managed profile to owner or other way around.
        if (mProfileSwitchMessageId != -1) {
@@ -791,26 +792,26 @@ public class ResolverActivity extends Activity {
        }
    }

    void onActivityStarted(TargetInfo cti) {
    public void onActivityStarted(TargetInfo cti) {
        // Do nothing
    }

    boolean shouldGetActivityMetadata() {
    public boolean shouldGetActivityMetadata() {
        return false;
    }

    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
        return true;
    }

    void showAppDetails(ResolveInfo ri) {
    public void showTargetDetails(ResolveInfo ri) {
        Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
                .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        startActivity(in);
    }

    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
            boolean filterLastUsed) {
        return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
@@ -820,7 +821,7 @@ public class ResolverActivity extends Activity {
    /**
     * Returns true if the activity is finishing and creation should halt
     */
    boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
    public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
            List<ResolveInfo> rList, boolean alwaysUseOption) {
        // The last argument of createAdapter is whether to do special handling
        // of the last used choice to highlight it in the list.  We need to always
@@ -867,7 +868,7 @@ public class ResolverActivity extends Activity {
        return false;
    }

    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
            boolean alwaysUseOption) {
        final boolean useHeader = adapter.hasFilteredItem();
        final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -898,7 +899,7 @@ public class ResolverActivity extends Activity {
                && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
    }

    final class DisplayResolveInfo implements TargetInfo {
    public final class DisplayResolveInfo implements TargetInfo {
        private final ResolveInfo mResolveInfo;
        private final CharSequence mDisplayLabel;
        private Drawable mDisplayIcon;
@@ -906,8 +907,9 @@ public class ResolverActivity extends Activity {
        private final CharSequence mExtendedInfo;
        private final Intent mResolvedIntent;
        private final List<Intent> mSourceIntents = new ArrayList<>();
        private boolean mPinned;

        DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
        public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
                CharSequence pInfo, Intent pOrigIntent) {
            mSourceIntents.add(originalIntent);
            mResolveInfo = pri;
@@ -932,6 +934,7 @@ public class ResolverActivity extends Activity {
            mExtendedInfo = other.mExtendedInfo;
            mResolvedIntent = new Intent(other.mResolvedIntent);
            mResolvedIntent.fillIn(fillInIntent, flags);
            mPinned = other.mPinned;
        }

        public ResolveInfo getResolveInfo() {
@@ -1026,6 +1029,15 @@ public class ResolverActivity extends Activity {
            activity.startActivityAsUser(mResolvedIntent, options, user);
            return false;
        }

        @Override
        public boolean isPinned() {
            return mPinned;
        }

        public void setPinned(boolean pinned) {
            mPinned = pinned;
        }
    }

    /**
@@ -1039,7 +1051,7 @@ public class ResolverActivity extends Activity {
         *
         * @return the resolved intent for this target
         */
        public Intent getResolvedIntent();
        Intent getResolvedIntent();

        /**
         * Get the resolved component name that represents this target. Note that this may not
@@ -1048,7 +1060,7 @@ public class ResolverActivity extends Activity {
         *
         * @return the resolved ComponentName for this target
         */
        public ComponentName getResolvedComponentName();
        ComponentName getResolvedComponentName();

        /**
         * Start the activity referenced by this target.
@@ -1057,7 +1069,7 @@ public class ResolverActivity extends Activity {
         * @param options ActivityOptions bundle
         * @return true if the start completed successfully
         */
        public boolean start(Activity activity, Bundle options);
        boolean start(Activity activity, Bundle options);

        /**
         * Start the activity referenced by this target as if the ResolverActivity's caller
@@ -1068,7 +1080,7 @@ public class ResolverActivity extends Activity {
         * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
         * @return true if the start completed successfully
         */
        public boolean startAsCaller(Activity activity, Bundle options, int userId);
        boolean startAsCaller(Activity activity, Bundle options, int userId);

        /**
         * Start the activity referenced by this target as a given user.
@@ -1078,7 +1090,7 @@ public class ResolverActivity extends Activity {
         * @param user handle for the user to start the activity as
         * @return true if the start completed successfully
         */
        public boolean startAsUser(Activity activity, Bundle options, UserHandle user);
        boolean startAsUser(Activity activity, Bundle options, UserHandle user);

        /**
         * Return the ResolveInfo about how and why this target matched the original query
@@ -1086,14 +1098,14 @@ public class ResolverActivity extends Activity {
         *
         * @return ResolveInfo representing this target's match
         */
        public ResolveInfo getResolveInfo();
        ResolveInfo getResolveInfo();

        /**
         * Return the human-readable text label for this target.
         *
         * @return user-visible target label
         */
        public CharSequence getDisplayLabel();
        CharSequence getDisplayLabel();

        /**
         * Return any extended info for this target. This may be used to disambiguate
@@ -1101,35 +1113,40 @@ public class ResolverActivity extends Activity {
         *
         * @return human-readable disambig string or null if none present
         */
        public CharSequence getExtendedInfo();
        CharSequence getExtendedInfo();

        /**
         * @return The drawable that should be used to represent this target
         */
        public Drawable getDisplayIcon();
        Drawable getDisplayIcon();

        /**
         * @return The (small) icon to badge the target with
         */
        public Drawable getBadgeIcon();
        Drawable getBadgeIcon();

        /**
         * @return The content description for the badge icon
         */
        public CharSequence getBadgeContentDescription();
        CharSequence getBadgeContentDescription();

        /**
         * Clone this target with the given fill-in information.
         */
        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
        TargetInfo cloneFilledIn(Intent fillInIntent, int flags);

        /**
         * @return the list of supported source intents deduped against this single target
         */
        public List<Intent> getAllSourceIntents();
        List<Intent> getAllSourceIntents();

        /**
         * @return true if this target should be pinned to the front by the request of the user
         */
        boolean isPinned();
    }

    class ResolveListAdapter extends BaseAdapter {
    public class ResolveListAdapter extends BaseAdapter {
        private final List<Intent> mIntents;
        private final Intent[] mInitialIntents;
        private final List<ResolveInfo> mBaseResolveList;
@@ -1376,9 +1393,12 @@ public class ResolverActivity extends Activity {
                    }
                }
                if (!found) {
                    into.add(new ResolvedComponentInfo(new ComponentName(
                            newInfo.activityInfo.packageName, newInfo.activityInfo.name),
                            intent, newInfo));
                    final ComponentName name = new ComponentName(
                            newInfo.activityInfo.packageName, newInfo.activityInfo.name);
                    final ResolvedComponentInfo rci = new ResolvedComponentInfo(name,
                            intent, newInfo);
                    rci.setPinned(isComponentPinned(name));
                    into.add(rci);
                }
            }
        }
@@ -1454,6 +1474,7 @@ public class ResolverActivity extends Activity {
            final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
            final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
                    extraInfo, replaceIntent);
            dri.setPinned(rci.isPinned());
            addResolveInfo(dri);
            if (replaceIntent == intent) {
                // Only add alternates if we didn't get a specific replacement from
@@ -1537,11 +1558,11 @@ public class ResolverActivity extends Activity {
            return false;
        }

        protected int getDisplayResolveInfoCount() {
        public int getDisplayResolveInfoCount() {
            return mDisplayList.size();
        }

        protected DisplayResolveInfo getDisplayResolveInfo(int index) {
        public DisplayResolveInfo getDisplayResolveInfo(int index) {
            // Used to query services. We only query services for primary targets, not alternates.
            return mDisplayList.get(index);
        }
@@ -1571,6 +1592,10 @@ public class ResolverActivity extends Activity {
            return !TextUtils.isEmpty(info.getExtendedInfo());
        }

        public boolean isComponentPinned(ComponentName name) {
            return false;
        }

        public final void bindView(int position, View view) {
            onBindView(view, getItem(position));
        }
@@ -1607,6 +1632,7 @@ public class ResolverActivity extends Activity {

    static final class ResolvedComponentInfo {
        public final ComponentName name;
        private boolean mPinned;
        private final List<Intent> mIntents = new ArrayList<>();
        private final List<ResolveInfo> mResolveInfos = new ArrayList<>();

@@ -1649,6 +1675,14 @@ public class ResolverActivity extends Activity {
            }
            return -1;
        }

        public boolean isPinned() {
            return mPinned;
        }

        public void setPinned(boolean pinned) {
            mPinned = pinned;
        }
    }

    static class ViewHolder {
@@ -1702,7 +1736,7 @@ public class ResolverActivity extends Activity {
                return false;
            }
            ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
            showAppDetails(ri);
            showTargetDetails(ri);
            return true;
        }

+22 −10
Original line number Diff line number Diff line
@@ -47,8 +47,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {

    private static final boolean DEBUG = false;

    // Two weeks
    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
    // One week
    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;

    private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;

@@ -171,6 +171,17 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
            }
        }

        final boolean lPinned = lhsp.isPinned();
        final boolean rPinned = rhsp.isPinned();

        if (lPinned && !rPinned) {
            return -1;
        } else if (!lPinned && rPinned) {
            return 1;
        }

        // Pinned items stay stable within a normal lexical sort and ignore scoring.
        if (!lPinned && !rPinned) {
            if (mStats != null) {
                final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
                        lhs.activityInfo.packageName, lhs.activityInfo.name));
@@ -182,6 +193,7 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
                    return diff > 0 ? 1 : -1;
                }
            }
        }

        CharSequence  sa = lhs.loadLabel(mPm);
        if (sa == null) sa = lhs.activityInfo.name;
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.internal.app;

import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;

import com.android.internal.R;

/**
 * Shows a dialog with actions to take on a chooser target
 */
public class ResolverTargetActionsDialogFragment extends DialogFragment
        implements DialogInterface.OnClickListener {
    private static final String NAME_KEY = "componentName";
    private static final String PINNED_KEY = "pinned";
    private static final String TITLE_KEY = "title";

    // Sync with R.array.resolver_target_actions_* resources
    private static final int TOGGLE_PIN_INDEX = 0;
    private static final int APP_INFO_INDEX = 1;

    public ResolverTargetActionsDialogFragment() {
    }

    public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
            boolean pinned) {
        Bundle args = new Bundle();
        args.putCharSequence(TITLE_KEY, title);
        args.putParcelable(NAME_KEY, name);
        args.putBoolean(PINNED_KEY, pinned);
        setArguments(args);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final Bundle args = getArguments();
        final int itemRes = args.getBoolean(PINNED_KEY, false)
                ? R.array.resolver_target_actions_unpin
                : R.array.resolver_target_actions_pin;
        return new Builder(getContext())
                .setCancelable(true)
                .setItems(itemRes, this)
                .setTitle(args.getCharSequence(TITLE_KEY))
                .create();
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        final Bundle args = getArguments();
        ComponentName name = args.getParcelable(NAME_KEY);
        switch (which) {
            case TOGGLE_PIN_INDEX:
                SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
                final String key = name.flattenToString();
                boolean currentVal = sp.getBoolean(name.flattenToString(), false);
                if (currentVal) {
                    sp.edit().remove(key).apply();
                } else {
                    sp.edit().putBoolean(key, true).apply();
                }

                // Force the chooser to requery and resort things
                getActivity().recreate();
                break;
            case APP_INFO_INDEX:
                Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                        .setData(Uri.fromParts("package", name.getPackageName(), null))
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
                startActivity(in);
                break;
        }
        dismiss();
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -402,4 +402,15 @@
        <item>@color/Red_700</item>
    </array>

    <!-- Used in ResolverTargetActionsDialogFragment -->
    <string-array name="resolver_target_actions_pin">
        <item>@string/pin_target</item>
        <item>@string/app_info</item>
    </string-array>

    <string-array name="resolver_target_actions_unpin">
        <item>@string/unpin_target</item>
        <item>@string/app_info</item>
    </string-array>

</resources>
Loading