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

Commit 46fed54b authored by Vinod Krishnan's avatar Vinod Krishnan
Browse files

Redo Move ClockworkPackageInstaller functionality here"

This reverts commit 5b26c2e6.

Change-Id: I88fa09f87023a7c1b2aac3100cfbdce6283de770
parent 5b26c2e6
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ LOCAL_STATIC_JAVA_LIBRARIES += \
    android-support-v14-preference \
    android-support-v17-preference-leanback \
    android-support-v17-leanback \
    xz-java

LOCAL_RESOURCE_DIR := \
    frameworks/support/v17/leanback/res \
+23 −0
Original line number Diff line number Diff line
@@ -16,8 +16,11 @@
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
    <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.KILL_UID" />

    <uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />

    <application android:label="@string/app_name"
            android:allowBackup="false"
            android:theme="@style/Theme.DialogWhenLarge"
@@ -99,6 +102,26 @@
                <action android:name="android.intent.action.GET_PERMISSIONS_COUNT" />
            </intent-filter>
        </receiver>

        <!-- Wearable Components -->
        <service android:name=".wear.WearPackageInstallerService"
                 android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
                 android:exported="true">
            <intent-filter>
                <action android:name="com.android.packageinstaller.wear.INSTALL_PACKAGE"/>
                <data android:scheme="content" android:mimeType="vnd.android.cursor.item/*"/>
            </intent-filter>
            <intent-filter>
                <action android:name="com.android.packageinstaller.wear.UNINSTALL_PACKAGE"/>
            </intent-filter>
        </service>

        <provider android:name=".wear.WearPackageIconProvider"
                  android:authorities="com.android.packageinstaller.wear.provider"
                  android:grantUriPermissions="true"
                  android:exported="true" />


    </application>

</manifest>
+202 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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.packageinstaller.wear;

import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;

public class WearPackageIconProvider extends ContentProvider {
    private static final String TAG = "WearPackageIconProvider";
    public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";

    private static final String REQUIRED_PERMISSION =
            "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";

    /** MIME types. */
    public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";

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

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        throw new UnsupportedOperationException("Query is not supported.");
    }

    @Override
    public String getType(Uri uri) {
        if (uri == null) {
            throw new IllegalArgumentException("URI passed in is null.");
        }

        if (AUTHORITY.equals(uri.getEncodedAuthority())) {
            return ICON_TYPE;
        }
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException("Insert is not supported.");
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        if (uri == null) {
            throw new IllegalArgumentException("URI passed in is null.");
        }

        enforcePermissions(uri);

        if (ICON_TYPE.equals(getType(uri))) {
            final File file = WearPackageUtil.getIconFile(
                    this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
            if (file != null) {
                file.delete();
            }
        }

        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException("Update is not supported.");
    }

    @Override
    public ParcelFileDescriptor openFile(
            Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
        if (uri == null) {
            throw new IllegalArgumentException("URI passed in is null.");
        }

        enforcePermissions(uri);

        if (ICON_TYPE.equals(getType(uri))) {
            final File file = WearPackageUtil.getIconFile(
                    this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
            if (file != null) {
                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
            }
        }
        return null;
    }

    public static Uri getUriForPackage(final String packageName) {
        return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
    }

    private String getPackageNameFromUri(Uri uri) {
        if (uri == null) {
            return null;
        }
        List<String> pathSegments = uri.getPathSegments();
        String packageName = pathSegments.get(pathSegments.size() - 1);

        if (packageName.endsWith(".icon")) {
            packageName = packageName.substring(0, packageName.lastIndexOf("."));
        }
        return packageName;
    }

    /**
     * Make sure the calling app is either a system app or the same app or has the right permission.
     * @throws SecurityException if the caller has insufficient permissions.
     */
    @TargetApi(Build.VERSION_CODES.BASE_1_1)
    private void enforcePermissions(Uri uri) {
        // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
        // allow System process to access this provider.
        Context context = getContext();
        final int pid = Binder.getCallingPid();
        final int uid = Binder.getCallingUid();
        final int myUid = android.os.Process.myUid();

        if (uid == myUid || isSystemApp(context, pid)) {
            return;
        }

        if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
            return;
        }

        // last chance, check against any uri grants
        if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
                == PERMISSION_GRANTED) {
            return;
        }

        throw new SecurityException("Permission Denial: reading "
                + getClass().getName() + " uri " + uri + " from pid=" + pid
                + ", uid=" + uid);
    }

    /**
     * From the pid of the calling process, figure out whether this is a system app or not. We do
     * this by checking the application information corresponding to the pid and then checking if
     * FLAG_SYSTEM is set.
     */
    @TargetApi(Build.VERSION_CODES.CUPCAKE)
    private boolean isSystemApp(Context context, int pid) {
        // Get the Activity Manager Object
        ActivityManager aManager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        // Get the list of running Applications
        List<ActivityManager.RunningAppProcessInfo> rapInfoList =
                aManager.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
            if (rapInfo.pid == pid) {
                try {
                    PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
                            rapInfo.pkgList[0], 0);
                    if (pkgInfo != null && pkgInfo.applicationInfo != null &&
                            (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                        Log.d(TAG, pid + " is a system app.");
                        return true;
                    }
                } catch (PackageManager.NameNotFoundException e) {
                    Log.e(TAG, "Could not find package information.", e);
                    return false;
                }
            }
        }
        return false;
    }
}
+608 −0

File added.

Preview size limit exceeded, changes collapsed.

+154 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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.packageinstaller.wear;

import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.Log;

import org.tukaani.xz.LZMAInputStream;
import org.tukaani.xz.XZInputStream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

public class WearPackageUtil {
    private static final String TAG = "WearablePkgInstaller";

    private static final String COMPRESSION_LZMA = "lzma";
    private static final String COMPRESSION_XZ = "xz";

    public static File getTemporaryFile(Context context, String packageName) {
        try {
            File newFileDir = new File(context.getFilesDir(), "tmp");
            newFileDir.mkdirs();
            Os.chmod(newFileDir.getAbsolutePath(), 0771);
            File newFile = new File(newFileDir, packageName + ".apk");
            return newFile;
        }   catch (ErrnoException e) {
            Log.e(TAG, "Failed to open.", e);
            return null;
        }
    }

    public static File getIconFile(final Context context, final String packageName) {
        try {
            File newFileDir = new File(context.getFilesDir(), "images/icons");
            newFileDir.mkdirs();
            Os.chmod(newFileDir.getAbsolutePath(), 0771);
            return new File(newFileDir, packageName + ".icon");
        }   catch (ErrnoException e) {
            Log.e(TAG, "Failed to open.", e);
            return null;
        }
    }

    /**
     * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
     * by the PackageManager, we will parse it before sending it to the PackageManager.
     * Unfortunately, PackageParser needs a file to parse. So, we have to temporarily convert the fd
     * to a File.
     *
     * @param context
     * @param fd FileDescriptor to convert to File
     * @param packageName Name of package, will define the name of the file
     * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
     *                       decompress it here
     */
    public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
            String packageName, @Nullable String compressionAlg) {
        File newFile = getTemporaryFile(context, packageName);
        if (fd == null || fd.getFileDescriptor() == null)  {
            return null;
        }
        InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
        try {
            if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
                fr = new XZInputStream(fr);
            } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
                fr = new LZMAInputStream(fr);
            }
        } catch (IOException e) {
            Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
            return null;
        }

        int nRead;
        byte[] data = new byte[1024];
        try {
            final FileOutputStream fo = new FileOutputStream(newFile);
            while ((nRead = fr.read(data, 0, data.length)) != -1) {
                fo.write(data, 0, nRead);
            }
            fo.flush();
            fo.close();
            Os.chmod(newFile.getAbsolutePath(), 0644);
            return newFile;
        } catch (IOException e) {
            Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
            return null;
        }   catch (ErrnoException e) {
            Log.e(TAG, "Could not set permissions on file ", e);
            return null;
        } finally {
            try {
                fr.close();
            } catch (IOException e) {
                Log.e(TAG, "Failed to close the file from FD ", e);
            }
        }
    }

    public static boolean hasLauncherActivity(PackageParser.Package pkg) {
        if (pkg == null || pkg.activities == null) {
            return false;
        }

        final int activityCount = pkg.activities.size();
        for (int i = 0; i < activityCount; ++i) {
            if (pkg.activities.get(i).intents != null) {
                ArrayList<PackageParser.ActivityIntentInfo> intents =
                        pkg.activities.get(i).intents;
                final int intentsCount = intents.size();
                for (int j = 0; j < intentsCount; ++j) {
                    final PackageParser.ActivityIntentInfo intentInfo = intents.get(j);
                    if (intentInfo.hasAction(Intent.ACTION_MAIN)) {
                        if (intentInfo.hasCategory(Intent.CATEGORY_INFO) ||
                                intentInfo .hasCategory(Intent.CATEGORY_LAUNCHER)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    public static boolean isWear(final Context context) {
        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
    }
}